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
2d6b5f8a
Unverified
Commit
2d6b5f8a
authored
Dec 03, 2019
by
Lyza Gardner
Committed by
GitHub
Dec 03, 2019
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #1554 from hypothesis/annotation-action-bar
Extract `AnnotationActionBar` Subcomponent from `Annotation`
parents
1bb4191c
ed3e31d5
Changes
13
Hide whitespace changes
Inline
Side-by-side
Showing
13 changed files
with
523 additions
and
233 deletions
+523
-233
annotation-action-bar.js
src/sidebar/components/annotation-action-bar.js
+107
-0
annotation-action-button.js
src/sidebar/components/annotation-action-button.js
+13
-8
annotation.js
src/sidebar/components/annotation.js
+0
-46
annotation-action-bar-test.js
src/sidebar/components/test/annotation-action-bar-test.js
+223
-0
annotation-action-button-test.js
src/sidebar/components/test/annotation-action-button-test.js
+15
-3
annotation-test.js
src/sidebar/components/test/annotation-test.js
+0
-129
index.js
src/sidebar/index.js
+2
-6
annotation.html
src/sidebar/templates/annotation.html
+7
-40
annotation-sharing.js
src/sidebar/util/annotation-sharing.js
+51
-0
annotation-sharing-test.js
src/sidebar/util/test/annotation-sharing-test.js
+100
-0
annotation-action-bar.scss
src/styles/sidebar/components/annotation-action-bar.scss
+3
-0
annotation-action-button.scss
src/styles/sidebar/components/annotation-action-button.scss
+1
-1
sidebar.scss
src/styles/sidebar/sidebar.scss
+1
-0
No files found.
src/sidebar/components/annotation-action-bar.js
0 → 100644
View file @
2d6b5f8a
'use strict'
;
const
propTypes
=
require
(
'prop-types'
);
const
{
createElement
}
=
require
(
'preact'
);
const
{
withServices
}
=
require
(
'../util/service-context'
);
const
{
isShareable
,
shareURI
}
=
require
(
'../util/annotation-sharing'
);
const
AnnotationActionButton
=
require
(
'./annotation-action-button'
);
const
AnnotationShareControl
=
require
(
'./annotation-share-control'
);
/**
* A collection of `AnnotationActionButton`s in the footer area of an annotation.
*/
function
AnnotationActionBar
({
annotation
,
isPrivate
,
onDelete
,
onEdit
,
onFlag
,
onReply
,
groups
,
permissions
,
session
,
settings
,
})
{
// Is the current user allowed to take the given `action` on this annotation?
const
userIsAuthorizedTo
=
action
=>
{
return
permissions
.
permits
(
annotation
.
permissions
,
action
,
session
.
state
.
userid
);
};
const
showDeleteAction
=
userIsAuthorizedTo
(
'delete'
);
const
showEditAction
=
userIsAuthorizedTo
(
'update'
);
// Anyone may flag an annotation except the annotation's author.
// This option is even presented to anonymous users
const
showFlagAction
=
session
.
state
.
userid
!==
annotation
.
user
;
const
showShareAction
=
isShareable
(
annotation
,
settings
);
const
annotationGroup
=
groups
.
get
(
annotation
.
group
);
return
(
<
div
className
=
"annotation-action-bar"
>
{
showEditAction
&&
(
<
AnnotationActionButton
icon
=
"edit"
label
=
"Edit"
onClick
=
{
onEdit
}
/
>
)}
{
showDeleteAction
&&
(
<
AnnotationActionButton
icon
=
"trash"
label
=
"Delete"
onClick
=
{
onDelete
}
/
>
)}
<
AnnotationActionButton
icon
=
"reply"
label
=
"Reply"
onClick
=
{
onReply
}
/
>
{
showShareAction
&&
(
<
AnnotationShareControl
group
=
{
annotationGroup
}
isPrivate
=
{
isPrivate
}
shareUri
=
{
shareURI
(
annotation
)}
/
>
)}
{
showFlagAction
&&
!
annotation
.
flagged
&&
(
<
AnnotationActionButton
icon
=
"flag"
label
=
"Report this annotation to moderators"
onClick
=
{
onFlag
}
/
>
)}
{
showFlagAction
&&
annotation
.
flagged
&&
(
<
AnnotationActionButton
isActive
=
{
true
}
icon
=
"flag--active"
label
=
"Annotation has been reported to the moderators"
/>
)}
<
/div
>
);
}
AnnotationActionBar
.
propTypes
=
{
annotation
:
propTypes
.
object
.
isRequired
,
/** Is this annotation shared at the group level or marked as "only me"/private? */
isPrivate
:
propTypes
.
bool
.
isRequired
,
/** Callbacks for when action buttons are clicked/tapped */
onEdit
:
propTypes
.
func
.
isRequired
,
onDelete
:
propTypes
.
func
.
isRequired
,
onFlag
:
propTypes
.
func
.
isRequired
,
onReply
:
propTypes
.
func
.
isRequired
,
// Injected services
groups
:
propTypes
.
object
.
isRequired
,
permissions
:
propTypes
.
object
.
isRequired
,
session
:
propTypes
.
object
.
isRequired
,
settings
:
propTypes
.
object
.
isRequired
,
};
AnnotationActionBar
.
injectedProps
=
[
'groups'
,
'permissions'
,
'session'
,
'settings'
,
];
module
.
exports
=
withServices
(
AnnotationActionBar
);
src/sidebar/components/annotation-action-button.js
View file @
2d6b5f8a
...
@@ -6,18 +6,21 @@ const { createElement } = require('preact');
...
@@ -6,18 +6,21 @@ const { createElement } = require('preact');
const
SvgIcon
=
require
(
'./svg-icon'
);
const
SvgIcon
=
require
(
'./svg-icon'
);
/**
* A simple icon-only button for actions applicable to annotations
*/
function
AnnotationActionButton
({
function
AnnotationActionButton
({
className
=
''
,
icon
,
icon
,
is
Disabled
,
is
Active
=
false
,
label
,
label
,
onClick
,
onClick
=
()
=>
null
,
})
{
})
{
return
(
return
(
<
button
<
button
className
=
{
classnames
(
'annotation-action-button'
,
className
)}
className
=
{
classnames
(
'annotation-action-button'
,
{
'is-active'
:
isActive
,
})}
onClick
=
{
onClick
}
onClick
=
{
onClick
}
disabled
=
{
isDisabled
}
aria
-
label
=
{
label
}
aria
-
label
=
{
label
}
title
=
{
label
}
title
=
{
label
}
>
>
...
@@ -27,12 +30,14 @@ function AnnotationActionButton({
...
@@ -27,12 +30,14 @@ function AnnotationActionButton({
}
}
AnnotationActionButton
.
propTypes
=
{
AnnotationActionButton
.
propTypes
=
{
className
:
propTypes
.
string
,
/** The name of the SVGIcon to render */
/** The name of the SVGIcon to render */
icon
:
propTypes
.
string
.
isRequired
,
icon
:
propTypes
.
string
.
isRequired
,
isDisabled
:
propTypes
.
bool
.
isRequired
,
/** Is this button currently in an "active" or "on" state? */
isActive
:
propTypes
.
bool
,
/** a label used for the `title` and `aria-label` attributes */
label
:
propTypes
.
string
.
isRequired
,
label
:
propTypes
.
string
.
isRequired
,
onClick
:
propTypes
.
func
.
isRequired
,
/** optional callback for clicks */
onClick
:
propTypes
.
func
,
};
};
module
.
exports
=
AnnotationActionButton
;
module
.
exports
=
AnnotationActionButton
;
src/sidebar/components/annotation.js
View file @
2d6b5f8a
...
@@ -8,7 +8,6 @@ const {
...
@@ -8,7 +8,6 @@ const {
}
=
require
(
'../util/annotation-metadata'
);
}
=
require
(
'../util/annotation-metadata'
);
const
events
=
require
(
'../events'
);
const
events
=
require
(
'../events'
);
const
{
isThirdPartyUser
}
=
require
(
'../util/account-id'
);
const
{
isThirdPartyUser
}
=
require
(
'../util/account-id'
);
const
serviceConfig
=
require
(
'../service-config'
);
/**
/**
* Return a copy of `annotation` with changes made in the editor applied.
* Return a copy of `annotation` with changes made in the editor applied.
...
@@ -26,23 +25,6 @@ function updateModel(annotation, changes, permissions) {
...
@@ -26,23 +25,6 @@ function updateModel(annotation, changes, permissions) {
});
});
}
}
/**
* Return true if share links are globally enabled.
*
* Share links will only be shown on annotation cards if this is true and if
* these links are included in API responses.
*/
function
shouldEnableShareLinks
(
settings
)
{
const
serviceConfig_
=
serviceConfig
(
settings
);
if
(
serviceConfig_
===
null
)
{
return
true
;
}
if
(
typeof
serviceConfig_
.
enableShareLinks
!==
'boolean'
)
{
return
true
;
}
return
serviceConfig_
.
enableShareLinks
;
}
// @ngInject
// @ngInject
function
AnnotationController
(
function
AnnotationController
(
$document
,
$document
,
...
@@ -65,8 +47,6 @@ function AnnotationController(
...
@@ -65,8 +47,6 @@ function AnnotationController(
const
self
=
this
;
const
self
=
this
;
let
newlyCreatedByHighlightButton
;
let
newlyCreatedByHighlightButton
;
const
enableShareLinks
=
shouldEnableShareLinks
(
settings
);
/** Save an annotation to the server. */
/** Save an annotation to the server. */
function
save
(
annot
)
{
function
save
(
annot
)
{
let
saved
;
let
saved
;
...
@@ -218,14 +198,6 @@ function AnnotationController(
...
@@ -218,14 +198,6 @@ function AnnotationController(
}
}
}
}
this
.
authorize
=
function
(
action
)
{
return
permissions
.
permits
(
self
.
annotation
.
permissions
,
action
,
session
.
state
.
userid
);
};
/**
/**
* @ngdoc method
* @ngdoc method
* @name annotation.AnnotationController#flag
* @name annotation.AnnotationController#flag
...
@@ -517,28 +489,10 @@ function AnnotationController(
...
@@ -517,28 +489,10 @@ function AnnotationController(
return
self
.
annotation
.
hidden
;
return
self
.
annotation
.
hidden
;
};
};
this
.
canFlag
=
function
()
{
// Users can flag any annotations except their own.
return
session
.
state
.
userid
!==
self
.
annotation
.
user
;
};
this
.
isFlagged
=
function
()
{
return
self
.
annotation
.
flagged
;
};
this
.
isReply
=
function
()
{
this
.
isReply
=
function
()
{
return
isReply
(
self
.
annotation
);
return
isReply
(
self
.
annotation
);
};
};
this
.
incontextLink
=
function
()
{
if
(
enableShareLinks
&&
self
.
annotation
.
links
)
{
return
(
self
.
annotation
.
links
.
incontext
||
self
.
annotation
.
links
.
html
||
''
);
}
return
''
;
};
/**
/**
* Sets whether or not the controls for expanding/collapsing the body of
* Sets whether or not the controls for expanding/collapsing the body of
* lengthy annotations should be shown.
* lengthy annotations should be shown.
...
...
src/sidebar/components/test/annotation-action-bar-test.js
0 → 100644
View file @
2d6b5f8a
'use strict'
;
const
{
createElement
}
=
require
(
'preact'
);
const
{
mount
}
=
require
(
'enzyme'
);
const
AnnotationActionBar
=
require
(
'../annotation-action-bar'
);
const
mockImportedComponents
=
require
(
'./mock-imported-components'
);
describe
(
'AnnotationActionBar'
,
()
=>
{
let
fakeAnnotation
;
let
fakeOnDelete
;
let
fakeOnEdit
;
let
fakeOnFlag
;
let
fakeOnReply
;
// Fake services
let
fakeGroups
;
let
fakePermissions
;
let
fakeSession
;
let
fakeSettings
;
// Fake dependencies
let
fakeIsShareable
;
function
createComponent
(
props
=
{})
{
return
mount
(
<
AnnotationActionBar
annotation
=
{
fakeAnnotation
}
isPrivate
=
{
false
}
onDelete
=
{
fakeOnDelete
}
onEdit
=
{
fakeOnEdit
}
onReply
=
{
fakeOnReply
}
onFlag
=
{
fakeOnFlag
}
groups
=
{
fakeGroups
}
permissions
=
{
fakePermissions
}
session
=
{
fakeSession
}
settings
=
{
fakeSettings
}
{...
props
}
/
>
);
}
const
allowOnly
=
action
=>
{
fakePermissions
.
permits
.
returns
(
false
);
fakePermissions
.
permits
.
withArgs
(
sinon
.
match
.
any
,
action
,
sinon
.
match
.
any
)
.
returns
(
true
);
};
const
disallowOnly
=
action
=>
{
fakePermissions
.
permits
.
withArgs
(
sinon
.
match
.
any
,
action
,
sinon
.
match
.
any
)
.
returns
(
false
);
};
const
getButton
=
(
wrapper
,
iconName
)
=>
{
return
wrapper
.
find
(
'AnnotationActionButton'
).
filter
({
icon
:
iconName
});
};
beforeEach
(()
=>
{
fakeAnnotation
=
{
group
:
'fakegroup'
,
permissions
:
{},
user
:
'acct:bar@foo.com'
,
};
fakeSession
=
{
state
:
{
userid
:
'acct:foo@bar.com'
,
},
};
fakeOnEdit
=
sinon
.
stub
();
fakeOnDelete
=
sinon
.
stub
();
fakeOnReply
=
sinon
.
stub
();
fakeOnFlag
=
sinon
.
stub
();
fakeGroups
=
{
get
:
sinon
.
stub
(),
};
fakePermissions
=
{
permits
:
sinon
.
stub
().
returns
(
true
),
};
fakeSettings
=
{};
fakeIsShareable
=
sinon
.
stub
().
returns
(
true
);
AnnotationActionBar
.
$imports
.
$mock
(
mockImportedComponents
());
AnnotationActionBar
.
$imports
.
$mock
({
'../util/annotation-sharing'
:
{
isShareable
:
fakeIsShareable
,
shareURI
:
sinon
.
stub
().
returns
(
'http://share.me'
),
},
});
});
afterEach
(()
=>
{
AnnotationActionBar
.
$imports
.
$restore
();
});
describe
(
'edit action button'
,
()
=>
{
it
(
'shows edit button if permissions allow'
,
()
=>
{
allowOnly
(
'update'
);
const
wrapper
=
createComponent
();
assert
.
isTrue
(
getButton
(
wrapper
,
'edit'
).
exists
());
});
it
(
'invokes `onEdit` callback when edit button clicked'
,
()
=>
{
allowOnly
(
'update'
);
const
button
=
getButton
(
createComponent
(),
'edit'
);
button
.
props
().
onClick
();
assert
.
calledOnce
(
fakeOnEdit
);
});
it
(
'does not show edit button if permissions do not allow'
,
()
=>
{
disallowOnly
(
'update'
);
const
wrapper
=
createComponent
();
assert
.
isFalse
(
getButton
(
wrapper
,
'edit'
).
exists
());
});
});
describe
(
'delete action button'
,
()
=>
{
it
(
'shows delete button if permissions allow'
,
()
=>
{
allowOnly
(
'delete'
);
const
wrapper
=
createComponent
();
assert
.
isTrue
(
getButton
(
wrapper
,
'trash'
).
exists
());
});
it
(
'invokes `onDelete` callback when delete button clicked'
,
()
=>
{
allowOnly
(
'delete'
);
const
button
=
getButton
(
createComponent
(),
'trash'
);
button
.
props
().
onClick
();
assert
.
calledOnce
(
fakeOnDelete
);
});
it
(
'does not show edit button if permissions do not allow'
,
()
=>
{
disallowOnly
(
'delete'
);
const
wrapper
=
createComponent
();
assert
.
isFalse
(
getButton
(
wrapper
,
'trash'
).
exists
());
});
});
describe
(
'reply action button'
,
()
=>
{
it
(
'shows the reply button (in all cases)'
,
()
=>
{
const
wrapper
=
createComponent
();
assert
.
isTrue
(
getButton
(
wrapper
,
'reply'
).
exists
());
});
it
(
'invokes `onReply` callback when reply button clicked'
,
()
=>
{
const
button
=
getButton
(
createComponent
(),
'reply'
);
button
.
props
().
onClick
();
assert
.
calledOnce
(
fakeOnReply
);
});
});
describe
(
'share action button'
,
()
=>
{
it
(
'shows share action button if annotation is shareable'
,
()
=>
{
const
wrapper
=
createComponent
();
assert
.
isTrue
(
wrapper
.
find
(
'AnnotationShareControl'
).
exists
());
});
it
(
'does not show share action button if annotation is not shareable'
,
()
=>
{
fakeIsShareable
.
returns
(
false
);
const
wrapper
=
createComponent
();
assert
.
isFalse
(
wrapper
.
find
(
'AnnotationShareControl'
).
exists
());
});
});
describe
(
'flag action button'
,
()
=>
{
it
(
'shows flag button if user is not author'
,
()
=>
{
const
wrapper
=
createComponent
();
assert
.
isTrue
(
getButton
(
wrapper
,
'flag'
).
exists
());
});
it
(
'invokes `onFlag` callback when flag button clicked'
,
()
=>
{
const
button
=
getButton
(
createComponent
(),
'flag'
);
button
.
props
().
onClick
();
assert
.
calledOnce
(
fakeOnFlag
);
});
it
(
'does not show flag action button if user is author'
,
()
=>
{
fakeAnnotation
.
user
=
'acct:foo@bar.com'
;
const
wrapper
=
createComponent
();
assert
.
isFalse
(
getButton
(
wrapper
,
'flag'
).
exists
());
});
context
(
'previously-flagged annotation'
,
()
=>
{
beforeEach
(()
=>
{
fakeAnnotation
.
flagged
=
true
;
});
it
(
'renders an active-state flag action button'
,
()
=>
{
const
wrapper
=
createComponent
();
assert
.
isTrue
(
getButton
(
wrapper
,
'flag--active'
).
exists
());
});
it
(
'does not set an `onClick` property for the flag action button'
,
()
=>
{
const
button
=
getButton
(
createComponent
(),
'flag--active'
);
assert
.
isUndefined
(
button
.
props
().
onClick
);
});
});
});
});
src/sidebar/components/test/annotation-action-button-test.js
View file @
2d6b5f8a
...
@@ -30,8 +30,20 @@ describe('AnnotationActionButton', () => {
...
@@ -30,8 +30,20 @@ describe('AnnotationActionButton', () => {
AnnotationActionButton
.
$imports
.
$restore
();
AnnotationActionButton
.
$imports
.
$restore
();
});
});
it
(
'applies any provided className to the button'
,
()
=>
{
it
(
'adds active className if `isActive` is `true`'
,
()
=>
{
const
wrapper
=
createComponent
({
className
:
'my-class'
});
const
wrapper
=
createComponent
({
isActive
:
true
});
assert
.
isTrue
(
wrapper
.
hasClass
(
'my-class'
));
assert
.
isTrue
(
wrapper
.
find
(
'button'
).
hasClass
(
'is-active'
));
});
it
(
'renders `SvgIcon` if icon property set'
,
()
=>
{
const
wrapper
=
createComponent
();
assert
.
equal
(
wrapper
.
find
(
'SvgIcon'
).
prop
(
'name'
),
'fakeIcon'
);
});
it
(
'invokes `onClick` callback when pressed'
,
()
=>
{
const
wrapper
=
createComponent
();
wrapper
.
find
(
'button'
).
simulate
(
'click'
);
assert
.
calledOnce
(
fakeOnClick
);
});
});
});
});
src/sidebar/components/test/annotation-test.js
View file @
2d6b5f8a
...
@@ -33,25 +33,6 @@ const groupFixtures = {
...
@@ -33,25 +33,6 @@ const groupFixtures = {
},
},
};
};
/**
* Returns the controller for the action button with the given `label`.
*
* @param {Element} annotationEl - Annotation element
* @param {string} label - Button label
*/
function
findActionButton
(
annotationEl
,
label
)
{
const
btns
=
Array
.
from
(
annotationEl
[
0
].
querySelectorAll
(
'annotation-action-button'
)
);
const
match
=
btns
.
find
(
function
(
btn
)
{
const
ctrl
=
angular
.
element
(
btn
).
controller
(
'annotationActionButton'
);
return
ctrl
.
label
===
label
;
});
return
match
?
angular
.
element
(
match
).
controller
(
'annotationActionButton'
)
:
null
;
}
describe
(
'annotation'
,
function
()
{
describe
(
'annotation'
,
function
()
{
describe
(
'updateModel()'
,
function
()
{
describe
(
'updateModel()'
,
function
()
{
const
updateModel
=
require
(
'../annotation'
).
updateModel
;
const
updateModel
=
require
(
'../annotation'
).
updateModel
;
...
@@ -877,20 +858,6 @@ describe('annotation', function() {
...
@@ -877,20 +858,6 @@ describe('annotation', function() {
});
});
});
});
describe
(
'#canFlag'
,
function
()
{
it
(
'returns false if the user signed in is the same as the author of the annotation'
,
function
()
{
const
ann
=
fixtures
.
defaultAnnotation
();
const
controller
=
createDirective
(
ann
).
controller
;
assert
.
isFalse
(
controller
.
canFlag
());
});
it
(
'returns true if the user signed in is different from the author of the annotation'
,
function
()
{
const
ann
=
fixtures
.
thirdPartyAnnotation
();
const
controller
=
createDirective
(
ann
).
controller
;
assert
.
isTrue
(
controller
.
canFlag
());
});
});
describe
(
'#shouldShowLicense'
,
function
()
{
describe
(
'#shouldShowLicense'
,
function
()
{
[
[
{
{
...
@@ -937,28 +904,6 @@ describe('annotation', function() {
...
@@ -937,28 +904,6 @@ describe('annotation', function() {
});
});
});
});
describe
(
'#authorize'
,
function
()
{
it
(
'passes the current permissions and logged-in user ID to the permissions service'
,
function
()
{
const
ann
=
fixtures
.
defaultAnnotation
();
ann
.
permissions
=
{
read
:
[
fakeSession
.
state
.
userid
],
delete
:
[
fakeSession
.
state
.
userid
],
update
:
[
fakeSession
.
state
.
userid
],
};
const
controller
=
createDirective
(
ann
).
controller
;
[
'update'
,
'delete'
].
forEach
(
function
(
action
)
{
controller
.
authorize
(
action
);
assert
.
calledWith
(
fakePermissions
.
permits
,
ann
.
permissions
,
action
,
fakeSession
.
state
.
userid
);
});
});
});
describe
(
'saving a new annotation'
,
function
()
{
describe
(
'saving a new annotation'
,
function
()
{
let
annotation
;
let
annotation
;
...
@@ -1186,53 +1131,6 @@ describe('annotation', function() {
...
@@ -1186,53 +1131,6 @@ describe('annotation', function() {
});
});
});
});
describe
(
'annotation links'
,
function
()
{
it
(
'uses the in-context links when available'
,
function
()
{
const
annotation
=
Object
.
assign
({},
fixtures
.
defaultAnnotation
(),
{
links
:
{
incontext
:
'https://hpt.is/deadbeef'
,
},
});
const
controller
=
createDirective
(
annotation
).
controller
;
assert
.
equal
(
controller
.
incontextLink
(),
annotation
.
links
.
incontext
);
});
it
(
'falls back to the HTML link when in-context links are missing'
,
function
()
{
const
annotation
=
Object
.
assign
({},
fixtures
.
defaultAnnotation
(),
{
links
:
{
html
:
'https://test.hypothes.is/a/deadbeef'
,
},
});
const
controller
=
createDirective
(
annotation
).
controller
;
assert
.
equal
(
controller
.
incontextLink
(),
annotation
.
links
.
html
);
});
it
(
'in-context link is blank when unknown'
,
function
()
{
const
annotation
=
fixtures
.
defaultAnnotation
();
const
controller
=
createDirective
(
annotation
).
controller
;
assert
.
equal
(
controller
.
incontextLink
(),
''
);
});
[
true
,
false
].
forEach
(
enableShareLinks
=>
{
it
(
'does not render links if share links are globally disabled'
,
()
=>
{
const
annotation
=
Object
.
assign
({},
fixtures
.
defaultAnnotation
(),
{
links
:
{
incontext
:
'https://hpt.is/deadbeef'
,
},
});
fakeSettings
.
services
=
[
{
enableShareLinks
,
},
];
const
controller
=
createDirective
(
annotation
).
controller
;
const
hasIncontextLink
=
controller
.
incontextLink
()
===
annotation
.
links
.
incontext
;
assert
.
equal
(
hasIncontextLink
,
enableShareLinks
);
});
});
});
[
[
{
{
context
:
'for moderators'
,
context
:
'for moderators'
,
...
@@ -1262,32 +1160,5 @@ describe('annotation', function() {
...
@@ -1262,32 +1160,5 @@ describe('annotation', function() {
);
);
});
});
});
});
it
(
'flags the annotation when the user clicks the "Flag" button'
,
function
()
{
fakeAnnotationMapper
.
flagAnnotation
.
returns
(
Promise
.
resolve
());
const
ann
=
Object
.
assign
(
fixtures
.
defaultAnnotation
(),
{
user
:
'acct:notCurrentUser@localhost'
,
});
const
el
=
createDirective
(
ann
).
element
;
const
flagBtn
=
findActionButton
(
el
,
'Report this annotation to the moderators'
);
flagBtn
.
onClick
();
assert
.
called
(
fakeAnnotationMapper
.
flagAnnotation
);
});
it
(
'highlights the "Flag" button if the annotation is flagged'
,
function
()
{
const
ann
=
Object
.
assign
(
fixtures
.
defaultAnnotation
(),
{
flagged
:
true
,
user
:
'acct:notCurrentUser@localhost'
,
});
const
el
=
createDirective
(
ann
).
element
;
const
flaggedBtn
=
findActionButton
(
el
,
'Annotation has been reported to the moderators'
);
assert
.
ok
(
flaggedBtn
);
});
});
});
});
});
src/sidebar/index.js
View file @
2d6b5f8a
...
@@ -139,8 +139,8 @@ function startAngularApp(config) {
...
@@ -139,8 +139,8 @@ function startAngularApp(config) {
wrapReactComponent
(
require
(
'./components/annotation-header'
))
wrapReactComponent
(
require
(
'./components/annotation-header'
))
)
)
.
component
(
.
component
(
'annotationActionB
utton
'
,
'annotationActionB
ar
'
,
wrapReactComponent
(
require
(
'./components/annotation-action-b
utton
'
))
wrapReactComponent
(
require
(
'./components/annotation-action-b
ar
'
))
)
)
.
component
(
.
component
(
'annotationPublishControl'
,
'annotationPublishControl'
,
...
@@ -150,10 +150,6 @@ function startAngularApp(config) {
...
@@ -150,10 +150,6 @@ function startAngularApp(config) {
'annotationQuote'
,
'annotationQuote'
,
wrapReactComponent
(
require
(
'./components/annotation-quote'
))
wrapReactComponent
(
require
(
'./components/annotation-quote'
))
)
)
.
component
(
'annotationShareControl'
,
wrapReactComponent
(
require
(
'./components/annotation-share-control'
))
)
.
component
(
'annotationThread'
,
require
(
'./components/annotation-thread'
))
.
component
(
'annotationThread'
,
require
(
'./components/annotation-thread'
))
.
component
(
.
component
(
'annotationViewerContent'
,
'annotationViewerContent'
,
...
...
src/sidebar/templates/annotation.html
View file @
2d6b5f8a
...
@@ -86,46 +86,13 @@
...
@@ -86,46 +86,13 @@
</div>
</div>
<div
class=
"annotation-actions"
ng-if=
"!vm.isSaving && !vm.editing() && vm.id()"
>
<div
class=
"annotation-actions"
ng-if=
"!vm.isSaving && !vm.editing() && vm.id()"
>
<div
ng-show=
"vm.isSaving"
>
Saving…
</div>
<annotation-action-bar
<annotation-action-button
annotation=
"vm.annotation"
icon=
"'edit'"
is-private=
"vm.state().isPrivate"
is-disabled=
"vm.isDeleted()"
on-delete=
"vm.delete()"
label=
"'Edit'"
on-flag=
"vm.flag()"
ng-show=
"vm.authorize('update') && !vm.isSaving"
on-edit=
"vm.edit()"
on-click=
"vm.edit()"
on-reply=
"vm.reply()"
></annotation-action-bar>
></annotation-action-button>
<annotation-action-button
icon=
"'trash'"
is-disabled=
"vm.isDeleted()"
label=
"'Delete'"
ng-show=
"vm.authorize('delete')"
on-click=
"vm.delete()"
></annotation-action-button>
<annotation-action-button
icon=
"'reply'"
is-disabled=
"vm.isDeleted()"
label=
"'Reply'"
on-click=
"vm.reply()"
></annotation-action-button>
<span
class=
"annotation-share-dialog-wrapper"
ng-if=
"vm.incontextLink()"
>
<annotation-share-control
group=
"vm.group()"
is-private=
"vm.state().isPrivate"
share-uri=
"vm.incontextLink()"
></annotation-share-control>
</span>
<span
ng-if=
"vm.canFlag()"
>
<annotation-action-button
icon=
"'flag'"
is-disabled=
"vm.isDeleted()"
label=
"'Report this annotation to the moderators'"
ng-if=
"!vm.isFlagged()"
on-click=
"vm.flag()"
></annotation-action-button>
<annotation-action-button
class-name=
"'annotation-action-button--active'"
icon=
"'flag--active'"
is-disabled=
"vm.isDeleted()"
label=
"'Annotation has been reported to the moderators'"
ng-if=
"vm.isFlagged()"
></annotation-action-button>
</span>
</div>
</div>
</footer>
</footer>
</div>
</div>
src/sidebar/util/annotation-sharing.js
0 → 100644
View file @
2d6b5f8a
'use strict'
;
const
serviceConfig
=
require
(
'../service-config'
);
/**
* Is the sharing of annotations enabled? Check for any defined `serviceConfig`,
* but default to `true` if none found.
*
* @param {object} settings
* @return {boolean}
*/
function
sharingEnabled
(
settings
)
{
const
serviceConfig_
=
serviceConfig
(
settings
);
if
(
serviceConfig_
===
null
)
{
return
true
;
}
if
(
typeof
serviceConfig_
.
enableShareLinks
!==
'boolean'
)
{
return
true
;
}
return
serviceConfig_
.
enableShareLinks
;
}
/**
* Return any defined standalone URI for this `annotation`, preferably the
* `incontext` URI, but fallback to `html` link if not present.
*
* @param {object} annotation
* @return {string|undefined}
*/
function
shareURI
(
annotation
)
{
const
links
=
annotation
.
links
;
return
links
&&
(
links
.
incontext
||
links
.
html
);
}
/**
* For an annotation to be "shareable", sharing links need to be enabled overall
* and the annotation itself needs to have a sharing URI.
*
* @param {object} annotation
* @param {object} settings
* @return {boolean}
*/
function
isShareable
(
annotation
,
settings
)
{
return
!!
(
sharingEnabled
(
settings
)
&&
shareURI
(
annotation
));
}
module
.
exports
=
{
sharingEnabled
,
shareURI
,
isShareable
,
};
src/sidebar/util/test/annotation-sharing-test.js
0 → 100644
View file @
2d6b5f8a
'use strict'
;
const
sharingUtil
=
require
(
'../annotation-sharing'
);
describe
(
'sidebar.util.annotation-sharing'
,
()
=>
{
let
fakeAnnotation
;
let
fakeServiceConfig
;
let
fakeServiceSettings
;
beforeEach
(()
=>
{
fakeServiceSettings
=
{};
fakeServiceConfig
=
sinon
.
stub
().
returns
(
fakeServiceSettings
);
fakeAnnotation
=
{
links
:
{
incontext
:
'https://www.example.com'
,
html
:
'https://www.example2.com'
,
},
};
sharingUtil
.
$imports
.
$mock
({
'../service-config'
:
fakeServiceConfig
,
});
});
afterEach
(()
=>
{
sharingUtil
.
$imports
.
$restore
();
});
describe
(
'#annotationSharingEnabled'
,
()
=>
{
it
(
'returns true if no service settings present'
,
()
=>
{
fakeServiceConfig
.
returns
(
null
);
assert
.
isTrue
(
sharingUtil
.
sharingEnabled
({}));
});
it
(
'returns true if service settings do not have a `enableShareLinks` prop'
,
()
=>
{
// service config is an empty object
assert
.
isTrue
(
sharingUtil
.
sharingEnabled
({}));
});
it
(
'returns true if service settings `enableShareLinks` is non-boolean'
,
()
=>
{
fakeServiceConfig
.
returns
({
enableShareLinks
:
'foo'
});
assert
.
isTrue
(
sharingUtil
.
sharingEnabled
({}));
});
it
(
'returns false if service settings really sets it to nope'
,
()
=>
{
fakeServiceConfig
.
returns
({
enableShareLinks
:
false
});
assert
.
isFalse
(
sharingUtil
.
sharingEnabled
({}));
});
});
describe
(
'#shareURI'
,
()
=>
{
it
(
'returns `incontext` link if set on annotation'
,
()
=>
{
assert
.
equal
(
sharingUtil
.
shareURI
(
fakeAnnotation
),
'https://www.example.com'
);
});
it
(
'returns `html` link if `incontext` link not set on annotation'
,
()
=>
{
delete
fakeAnnotation
.
links
.
incontext
;
assert
.
equal
(
sharingUtil
.
shareURI
(
fakeAnnotation
),
'https://www.example2.com'
);
});
it
(
'returns `undefined` if links empty'
,
()
=>
{
delete
fakeAnnotation
.
links
.
incontext
;
delete
fakeAnnotation
.
links
.
html
;
assert
.
isUndefined
(
sharingUtil
.
shareURI
(
fakeAnnotation
));
});
it
(
'returns `undefined` if no links on annotation'
,
()
=>
{
delete
fakeAnnotation
.
links
;
assert
.
isUndefined
(
sharingUtil
.
shareURI
(
fakeAnnotation
));
});
});
describe
(
'#isShareable'
,
()
=>
{
it
(
'returns `true` if sharing enabled and there is a share link available'
,
()
=>
{
fakeServiceConfig
.
returns
(
null
);
assert
.
isTrue
(
sharingUtil
.
isShareable
(
fakeAnnotation
,
{}));
});
it
(
'returns `false` if sharing not enabled'
,
()
=>
{
fakeServiceConfig
.
returns
({
enableShareLinks
:
false
});
assert
.
isFalse
(
sharingUtil
.
isShareable
(
fakeAnnotation
,
{}));
});
it
(
'returns `false` if no sharing link available on annotation'
,
()
=>
{
fakeServiceConfig
.
returns
(
null
);
delete
fakeAnnotation
.
links
;
assert
.
isFalse
(
sharingUtil
.
isShareable
(
fakeAnnotation
,
{}));
});
});
});
src/styles/sidebar/components/annotation-action-bar.scss
0 → 100644
View file @
2d6b5f8a
.annotation-action-bar
{
display
:
flex
;
}
src/styles/sidebar/components/annotation-action-button.scss
View file @
2d6b5f8a
...
@@ -4,7 +4,7 @@
...
@@ -4,7 +4,7 @@
.annotation-action-button
{
.annotation-action-button
{
@include
buttons
.
button-base
;
@include
buttons
.
button-base
;
&
-
-active
{
&
.is
-active
{
color
:
var
.
$brand
;
color
:
var
.
$brand
;
&
:hover
{
&
:hover
{
...
...
src/styles/sidebar/sidebar.scss
View file @
2d6b5f8a
...
@@ -25,6 +25,7 @@
...
@@ -25,6 +25,7 @@
// Components
// Components
// ----------
// ----------
@use
'./components/action-button'
;
@use
'./components/action-button'
;
@use
'./components/annotation-action-bar'
;
@use
'./components/annotation-action-button'
;
@use
'./components/annotation-action-button'
;
@use
'./components/annotation'
;
@use
'./components/annotation'
;
@use
'./components/annotation-body'
;
@use
'./components/annotation-body'
;
...
...
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