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
c7ad6c67
Commit
c7ad6c67
authored
Apr 03, 2024
by
Alejandro Celaya
Committed by
Alejandro Celaya
Apr 08, 2024
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Highlight annotations when they are applied from pending updates
parent
f2e80a6d
Changes
9
Show whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
102 additions
and
7 deletions
+102
-7
Annotation.tsx
src/sidebar/components/Annotation/Annotation.tsx
+4
-1
Annotation-test.js
src/sidebar/components/Annotation/test/Annotation-test.js
+14
-0
AnnotationView.tsx
src/sidebar/components/AnnotationView.tsx
+0
-2
ThreadCard.tsx
src/sidebar/components/ThreadCard.tsx
+6
-1
ThreadCard-test.js
src/sidebar/components/test/ThreadCard-test.js
+13
-0
streamer.ts
src/sidebar/services/streamer.ts
+10
-0
streamer-test.js
src/sidebar/services/test/streamer-test.js
+28
-1
annotations.ts
src/sidebar/store/modules/annotations.ts
+11
-2
annotations-test.js
src/sidebar/store/modules/test/annotations-test.js
+16
-0
No files found.
src/sidebar/components/Annotation/Annotation.tsx
View file @
c7ad6c67
...
...
@@ -105,11 +105,14 @@ function Annotation({
const
annotationDescription
=
isSaved
(
annotation
)
?
annotationRole
(
annotation
)
:
`New
${
annotationRole
(
annotation
).
toLowerCase
()}
`
;
const
state
=
store
.
isAnnotationHighlighted
(
annotation
)
?
' - Highlighted'
:
''
;
return
(
<
article
className=
"space-y-4"
aria
-
label=
{
`${annotationDescription} by ${authorName}`
}
aria
-
label=
{
`${annotationDescription} by ${authorName}
${state}
`
}
>
<
AnnotationHeader
annotation=
{
annotation
}
...
...
src/sidebar/components/Annotation/test/Annotation-test.js
View file @
c7ad6c67
...
...
@@ -3,6 +3,7 @@ import {
mockImportedComponents
,
}
from
'@hypothesis/frontend-testing'
;
import
{
mount
}
from
'enzyme'
;
import
sinon
from
'sinon'
;
import
*
as
fixtures
from
'../../../test/annotation-fixtures'
;
import
Annotation
,
{
$imports
}
from
'../Annotation'
;
...
...
@@ -64,6 +65,7 @@ describe('Annotation', () => {
isSavingAnnotation
:
sinon
.
stub
().
returns
(
false
),
profile
:
sinon
.
stub
().
returns
({
userid
:
'acct:foo@bar.com'
}),
setExpanded
:
sinon
.
stub
(),
isAnnotationHighlighted
:
sinon
.
stub
().
returns
(
false
),
};
$imports
.
$mock
(
mockImportedComponents
());
...
...
@@ -96,6 +98,18 @@ describe('Annotation', () => {
'New annotation by Richard Lionheart'
,
);
});
[
true
,
false
].
forEach
(
isHighlighted
=>
{
it
(
'should mention if annotation is highlighted'
,
()
=>
{
fakeStore
.
isAnnotationHighlighted
.
returns
(
isHighlighted
);
const
wrapper
=
createComponent
();
assert
.
equal
(
wrapper
.
find
(
'article'
).
prop
(
'aria-label'
).
endsWith
(
' - Highlighted'
),
isHighlighted
,
);
});
});
});
describe
(
'annotation quote'
,
()
=>
{
...
...
src/sidebar/components/AnnotationView.tsx
View file @
c7ad6c67
...
...
@@ -60,8 +60,6 @@ function AnnotationView({
// not shown until the user expands the thread.
annots
.
forEach
(
annot
=>
annot
.
id
&&
store
.
setExpanded
(
annot
.
id
,
true
));
// FIXME - This should show a visual indication of which reply the
// annotation ID in the URL refers to. That isn't currently working.
if
(
topLevelAnnot
.
id
!==
annotationId
)
{
store
.
highlightAnnotations
([
annotationId
]);
}
...
...
src/sidebar/components/ThreadCard.tsx
View file @
c7ad6c67
import
{
Card
,
CardContent
}
from
'@hypothesis/frontend-shared'
;
import
classnames
from
'classnames'
;
import
debounce
from
'lodash.debounce'
;
import
{
useCallback
,
useEffect
,
useMemo
,
useRef
}
from
'preact/hooks'
;
...
...
@@ -29,6 +30,7 @@ function ThreadCard({ frameSync, thread }: ThreadCardProps) {
debounce
((
ann
:
Annotation
|
null
)
=>
frameSync
.
hoverAnnotation
(
ann
),
10
),
[
frameSync
],
);
const
isHighlighted
=
store
.
isAnnotationHighlighted
(
thread
.
annotation
);
const
scrollToAnnotation
=
useCallback
(
(
ann
:
Annotation
)
=>
{
...
...
@@ -65,7 +67,10 @@ function ThreadCard({ frameSync, thread }: ThreadCardProps) {
return
(
<
Card
active=
{
isHovered
}
classes=
"cursor-pointer focus-visible-ring theme-clean:border-none"
classes=
{
classnames
(
'cursor-pointer focus-visible-ring theme-clean:border-none'
,
{
'border-brand'
:
isHighlighted
},
)
}
data
-
testid=
"thread-card"
elementRef=
{
cardRef
}
tabIndex=
{
-
1
}
...
...
src/sidebar/components/test/ThreadCard-test.js
View file @
c7ad6c67
...
...
@@ -36,6 +36,7 @@ describe('ThreadCard', () => {
clearAnnotationFocusRequest
:
sinon
.
stub
(),
isAnnotationHovered
:
sinon
.
stub
().
returns
(
false
),
route
:
sinon
.
stub
(),
isAnnotationHighlighted
:
sinon
.
stub
().
returns
(
false
),
};
fakeThread
=
{
...
...
@@ -141,6 +142,18 @@ describe('ThreadCard', () => {
});
});
[
true
,
false
].
forEach
(
isHighlighted
=>
{
it
(
'applies UI changes when annotation is highlighted'
,
()
=>
{
fakeStore
.
isAnnotationHighlighted
.
returns
(
isHighlighted
);
const
wrapper
=
createComponent
();
assert
.
equal
(
wrapper
.
find
(
'Card'
).
prop
(
'classes'
).
includes
(
'border-brand'
),
isHighlighted
,
);
});
});
it
(
'should pass a11y checks'
,
checkAccessibility
({
...
...
src/sidebar/services/streamer.ts
View file @
c7ad6c67
...
...
@@ -52,6 +52,9 @@ export class StreamerService {
*/
private
_reconnectionAttempts
:
number
;
// Test seam
private
_window
:
Window
;
/** The randomly generated session ID */
clientId
:
string
;
...
...
@@ -61,6 +64,7 @@ export class StreamerService {
auth
:
AuthService
,
groups
:
GroupsService
,
session
:
SessionService
,
$window
:
Window
,
)
{
this
.
_auth
=
auth
;
this
.
_groups
=
groups
;
...
...
@@ -75,6 +79,7 @@ export class StreamerService {
this
.
_configMessages
=
{};
this
.
_reconnectionAttempts
=
0
;
this
.
_reconnectSetUp
=
false
;
this
.
_window
=
$window
;
}
/**
...
...
@@ -85,6 +90,11 @@ export class StreamerService {
const
updates
=
Object
.
values
(
this
.
_store
.
pendingUpdates
());
if
(
updates
.
length
)
{
this
.
_store
.
addAnnotations
(
updates
);
// Highlight the new/edited annotations for 5 seconds
this
.
_store
.
highlightAnnotations
(
updates
.
map
(({
id
})
=>
id
).
filter
(
Boolean
)
as
string
[],
);
this
.
_window
.
setTimeout
(()
=>
this
.
_store
.
highlightAnnotations
([]),
5000
);
}
const
deletions
=
Object
.
keys
(
this
.
_store
.
pendingDeletions
()).
map
(
id
=>
({
...
...
src/sidebar/services/test/streamer-test.js
View file @
c7ad6c67
import
{
delay
}
from
'@hypothesis/frontend-testing'
;
import
sinon
from
'sinon'
;
import
EventEmitter
from
'tiny-emitter'
;
import
{
promiseWithResolvers
}
from
'../../../shared/promise-with-resolvers'
;
import
{
fakeReduxStore
}
from
'../../test/fake-redux-store'
;
import
{
StreamerService
,
$imports
}
from
'../streamer'
;
...
...
@@ -81,6 +83,7 @@ describe('StreamerService', () => {
let
fakeSession
;
let
fakeWarnOnce
;
let
activeStreamer
;
let
fakeSetTimeout
;
function
createDefaultStreamer
()
{
activeStreamer
=
new
StreamerService
(
...
...
@@ -89,9 +92,22 @@ describe('StreamerService', () => {
fakeAuth
,
fakeGroups
,
fakeSession
,
{
setTimeout
:
fakeSetTimeout
},
);
}
function
timeoutAsPromise
()
{
const
{
resolve
,
promise
}
=
promiseWithResolvers
();
fakeSetTimeout
.
callsFake
(
callback
=>
setTimeout
(()
=>
{
callback
();
resolve
();
},
0
),
);
return
promise
;
}
beforeEach
(()
=>
{
fakeAPIRoutes
=
{
links
:
sinon
.
stub
().
resolves
({
websocket
:
'ws://example.com/ws'
}),
...
...
@@ -114,6 +130,7 @@ describe('StreamerService', () => {
}),
receiveRealTimeUpdates
:
sinon
.
stub
(),
removeAnnotations
:
sinon
.
stub
(),
highlightAnnotations
:
sinon
.
stub
(),
},
);
...
...
@@ -127,6 +144,7 @@ describe('StreamerService', () => {
};
fakeWarnOnce
=
sinon
.
stub
();
fakeSetTimeout
=
sinon
.
stub
();
$imports
.
$mock
({
'../../shared/warn-once'
:
{
warnOnce
:
fakeWarnOnce
},
...
...
@@ -388,7 +406,8 @@ describe('StreamerService', () => {
return
activeStreamer
.
connect
();
});
it
(
'applies updates immediately'
,
()
=>
{
it
(
'applies updates immediately and highlights annotations'
,
async
()
=>
{
const
timeoutPromise
=
timeoutAsPromise
();
const
[
ann
]
=
fixtures
.
createNotification
.
payload
;
fakeStore
.
pendingUpdates
.
returns
({
[
ann
.
id
]:
ann
,
...
...
@@ -403,6 +422,14 @@ describe('StreamerService', () => {
fakeStore
.
addAnnotations
,
fixtures
.
createNotification
.
payload
,
);
assert
.
calledWith
(
fakeStore
.
highlightAnnotations
,
fixtures
.
createNotification
.
payload
.
map
(({
id
})
=>
id
),
);
assert
.
calledWith
(
fakeSetTimeout
,
sinon
.
match
.
func
,
5000
);
await
timeoutPromise
;
assert
.
calledWith
(
fakeStore
.
highlightAnnotations
,
[]);
});
});
...
...
src/sidebar/store/modules/annotations.ts
View file @
c7ad6c67
...
...
@@ -384,8 +384,9 @@ function hideAnnotation(id: string) {
/**
* Highlight annotations with the given `ids`.
*
* This is used to indicate the specific annotation in a thread that was
* linked to for example. Replaces the current map of highlighted annotations.
* This is used to add a visual indicator to specific annotation cards, like a
* thread that was linked or annotations from pending updates that were applied.
* Replaces the current map of highlighted annotations.
* All provided annotations (`ids`) will be set to `true` in the `highlighted`
* map.
*/
...
...
@@ -498,6 +499,13 @@ const highlightedAnnotations = createSelector(
highlighted
=>
trueKeys
(
highlighted
),
);
/**
* Is the annotation currently highlighted?
*/
function
isAnnotationHighlighted
(
state
:
State
,
annotation
?:
Annotation
)
{
return
!!
annotation
?.
id
&&
state
.
highlighted
[
annotation
.
id
]
===
true
;
}
/**
* Is the annotation identified by `$tag` currently hovered?
*/
...
...
@@ -581,6 +589,7 @@ export const annotationsModule = createStoreModule(initialState, {
findIDsForTags
,
hoveredAnnotations
,
highlightedAnnotations
,
isAnnotationHighlighted
,
isAnnotationHovered
,
isWaitingToAnchorAnnotations
,
newAnnotations
,
...
...
src/sidebar/store/modules/test/annotations-test.js
View file @
c7ad6c67
...
...
@@ -527,4 +527,20 @@ describe('sidebar/store/modules/annotations', () => {
assert
.
isTrue
(
store
.
annotationExists
(
annot
.
id
));
});
});
describe
(
'isAnnotationHighlighted'
,
()
=>
{
[
{
annotation
:
undefined
,
expectedResult
:
false
},
{
annotation
:
{},
expectedResult
:
false
},
{
annotation
:
{
id
:
'1'
},
expectedResult
:
true
},
{
annotation
:
{
id
:
'2'
},
expectedResult
:
true
},
{
annotation
:
{
id
:
'3'
},
expectedResult
:
false
},
].
forEach
(({
annotation
,
expectedResult
})
=>
{
it
(
'returns true if the annotation ID is in the set of highlighted annotations'
,
()
=>
{
const
store
=
createTestStore
();
store
.
highlightAnnotations
([
'1'
,
'2'
]);
assert
.
equal
(
store
.
isAnnotationHighlighted
(
annotation
),
expectedResult
);
});
});
});
});
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