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
96081234
Commit
96081234
authored
Aug 10, 2020
by
Kyle Keating
Committed by
Robert Knight
Aug 12, 2020
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
improve typechecking
- thread-list - thread-card - thread - timestamp
parent
2a96432a
Changes
12
Show whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
76 additions
and
32 deletions
+76
-32
annotation-header.js
src/sidebar/components/annotation-header.js
+1
-4
annotation-share-info.js
src/sidebar/components/annotation-share-info.js
+7
-9
annotation.js
src/sidebar/components/annotation.js
+2
-1
annotation-share-info-test.js
src/sidebar/components/test/annotation-share-info-test.js
+1
-1
thread-list-test.js
src/sidebar/components/test/thread-list-test.js
+9
-0
thread-card.js
src/sidebar/components/thread-card.js
+14
-3
thread-list.js
src/sidebar/components/thread-list.js
+20
-3
thread.js
src/sidebar/components/thread.js
+11
-0
timestamp.js
src/sidebar/components/timestamp.js
+9
-7
annotations.js
src/sidebar/store/modules/annotations.js
+1
-0
tsconfig.json
src/tsconfig.json
+0
-3
api.js
src/types/api.js
+1
-1
No files found.
src/sidebar/components/annotation-header.js
View file @
96081234
...
@@ -27,10 +27,7 @@ export default function AnnotationHeader({
...
@@ -27,10 +27,7 @@ export default function AnnotationHeader({
const
isCollapsedReply
=
isReply
(
annotation
)
&&
threadIsCollapsed
;
const
isCollapsedReply
=
isReply
(
annotation
)
&&
threadIsCollapsed
;
const
setExpanded
=
useStore
(
store
=>
store
.
setExpanded
);
const
setExpanded
=
useStore
(
store
=>
store
.
setExpanded
);
const
annotationIsPrivate
=
isPrivate
(
const
annotationIsPrivate
=
isPrivate
(
annotation
.
permissions
);
annotation
.
permissions
,
annotation
.
user
);
const
annotationLink
=
annotation
.
links
?
annotation
.
links
.
html
:
''
;
const
annotationLink
=
annotation
.
links
?
annotation
.
links
.
html
:
''
;
// NB: `created` and `updated` are strings, not `Date`s
// NB: `created` and `updated` are strings, not `Date`s
...
...
src/sidebar/components/annotation-share-info.js
View file @
96081234
...
@@ -6,6 +6,10 @@ import { isPrivate } from '../util/permissions';
...
@@ -6,6 +6,10 @@ import { isPrivate } from '../util/permissions';
import
SvgIcon
from
'../../shared/components/svg-icon'
;
import
SvgIcon
from
'../../shared/components/svg-icon'
;
/**
* @typedef {import('../../types/api').Group} Group
*/
/**
/**
* Render information about what group an annotation is in and
* Render information about what group an annotation is in and
* whether it is private to the current user (only me)
* whether it is private to the current user (only me)
...
@@ -13,21 +17,15 @@ import SvgIcon from '../../shared/components/svg-icon';
...
@@ -13,21 +17,15 @@ import SvgIcon from '../../shared/components/svg-icon';
function
AnnotationShareInfo
({
annotation
})
{
function
AnnotationShareInfo
({
annotation
})
{
const
group
=
useStore
(
store
=>
store
.
getGroup
(
annotation
.
group
));
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
// 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
// URL (link) returned by the API for this group. Some groups do not have links
const
linkToGroup
=
hasGroup
&&
group
.
links
&&
group
.
links
.
html
;
const
linkToGroup
=
group
?
.
links
.
html
;
const
annotationIsPrivate
=
isPrivate
(
const
annotationIsPrivate
=
isPrivate
(
annotation
.
permissions
);
annotation
.
permissions
,
annotation
.
user
);
return
(
return
(
<
div
className
=
"annotation-share-info u-layout-row--align-baseline"
>
<
div
className
=
"annotation-share-info u-layout-row--align-baseline"
>
{
linkToGroup
&&
(
{
group
&&
linkToGroup
&&
(
<
a
<
a
className
=
"u-layout-row--align-baseline u-color-text--muted"
className
=
"u-layout-row--align-baseline u-color-text--muted"
href
=
{
group
.
links
.
html
}
href
=
{
group
.
links
.
html
}
...
...
src/sidebar/components/annotation.js
View file @
96081234
...
@@ -53,7 +53,8 @@ function Annotation({
...
@@ -53,7 +53,8 @@ function Annotation({
const
toggleText
=
`
${
toggleAction
}
(
${
replyCount
}
)`
;
const
toggleText
=
`
${
toggleAction
}
(
${
replyCount
}
)`
;
const
shouldShowActions
=
!
isSaving
&&
!
isEditing
;
const
shouldShowActions
=
!
isSaving
&&
!
isEditing
;
const
shouldShowLicense
=
isEditing
&&
!
isPrivate
&&
group
.
type
!==
'private'
;
const
shouldShowLicense
=
isEditing
&&
!
isPrivate
&&
group
&&
group
.
type
!==
'private'
;
const
shouldShowReplyToggle
=
replyCount
>
0
&&
!
isReply
(
annotation
);
const
shouldShowReplyToggle
=
replyCount
>
0
&&
!
isReply
(
annotation
);
const
onEditTags
=
({
tags
})
=>
{
const
onEditTags
=
({
tags
})
=>
{
...
...
src/sidebar/components/test/annotation-share-info-test.js
View file @
96081234
...
@@ -108,7 +108,7 @@ describe('AnnotationShareInfo', () => {
...
@@ -108,7 +108,7 @@ describe('AnnotationShareInfo', () => {
});
});
it
(
'should show "only me" text for annotation in third-party group'
,
()
=>
{
it
(
'should show "only me" text for annotation in third-party group'
,
()
=>
{
fakeGetGroup
.
returns
({
name
:
'Some Name'
});
fakeGetGroup
.
returns
({
name
:
'Some Name'
,
links
:
{}
});
const
wrapper
=
createAnnotationShareInfo
();
const
wrapper
=
createAnnotationShareInfo
();
const
privacyText
=
wrapper
.
find
(
const
privacyText
=
wrapper
.
find
(
...
...
src/sidebar/components/test/thread-list-test.js
View file @
96081234
...
@@ -103,6 +103,15 @@ describe('ThreadList', () => {
...
@@ -103,6 +103,15 @@ describe('ThreadList', () => {
wrapper
.
setProps
({});
wrapper
.
setProps
({});
};
};
context
(
'invalid scroll container'
,
()
=>
{
it
(
'should throw an error if the scroll container is missing'
,
()
=>
{
fakeScrollContainer
.
remove
();
assert
.
throws
(()
=>
{
createComponent
();
},
'Scroll container is missing'
);
});
});
context
(
'new annotation created in application'
,
()
=>
{
context
(
'new annotation created in application'
,
()
=>
{
it
(
'clears the current selection in the store'
,
()
=>
{
it
(
'clears the current selection in the store'
,
()
=>
{
const
wrapper
=
createComponent
();
const
wrapper
=
createComponent
();
...
...
src/sidebar/components/thread-card.js
View file @
96081234
...
@@ -10,11 +10,24 @@ import { withServices } from '../util/service-context';
...
@@ -10,11 +10,24 @@ import { withServices } from '../util/service-context';
import
Thread
from
'./thread'
;
import
Thread
from
'./thread'
;
/**
* @typedef {import('../../types/config').MergedConfig} MergedConfig
*/
/**
* @typedef ThreadCardProps
* @prop {Thread} thread
* @prop {Object} frameSync - Injected service
* @prop {MergedConfig} [settings] - Injected service
*/
/**
/**
* A "top-level" `Thread`, rendered as a "card" in the sidebar. A `Thread`
* A "top-level" `Thread`, rendered as a "card" in the sidebar. A `Thread`
* renders its own child `Thread`s within itself.
* renders its own child `Thread`s within itself.
*
* @param {ThreadCardProps} props
*/
*/
function
ThreadCard
({
frameSync
,
settings
=
{}
,
thread
})
{
function
ThreadCard
({
frameSync
,
settings
,
thread
})
{
const
threadTag
=
thread
.
annotation
&&
thread
.
annotation
.
$tag
;
const
threadTag
=
thread
.
annotation
&&
thread
.
annotation
.
$tag
;
const
isFocused
=
useStore
(
store
=>
store
.
isAnnotationFocused
(
threadTag
));
const
isFocused
=
useStore
(
store
=>
store
.
isAnnotationFocused
(
threadTag
));
const
showDocumentInfo
=
useStore
(
store
=>
store
.
route
()
!==
'sidebar'
);
const
showDocumentInfo
=
useStore
(
store
=>
store
.
route
()
!==
'sidebar'
);
...
@@ -67,8 +80,6 @@ function ThreadCard({ frameSync, settings = {}, thread }) {
...
@@ -67,8 +80,6 @@ function ThreadCard({ frameSync, settings = {}, thread }) {
ThreadCard
.
propTypes
=
{
ThreadCard
.
propTypes
=
{
thread
:
propTypes
.
object
.
isRequired
,
thread
:
propTypes
.
object
.
isRequired
,
/** injected */
frameSync
:
propTypes
.
object
.
isRequired
,
frameSync
:
propTypes
.
object
.
isRequired
,
settings
:
propTypes
.
object
,
settings
:
propTypes
.
object
,
};
};
...
...
src/sidebar/components/thread-list.js
View file @
96081234
...
@@ -13,14 +13,25 @@ import {
...
@@ -13,14 +13,25 @@ import {
import
ThreadCard
from
'./thread-card'
;
import
ThreadCard
from
'./thread-card'
;
/** @typedef {import('../util/build-thread').Thread} Thread */
// The precision of the `scrollPosition` value in pixels; values will be rounded
// The precision of the `scrollPosition` value in pixels; values will be rounded
// down to the nearest multiple of this scale value
// down to the nearest multiple of this scale value
const
SCROLL_PRECISION
=
50
;
const
SCROLL_PRECISION
=
50
;
function
getScrollContainer
()
{
function
getScrollContainer
()
{
return
document
.
querySelector
(
'.js-thread-list-scroll-root'
);
const
container
=
document
.
querySelector
(
'.js-thread-list-scroll-root'
);
if
(
!
container
)
{
throw
new
Error
(
'Scroll container is missing'
);
}
return
container
;
}
}
/**
* @typedef ThreadListProps
* @prop {Thread} thread
*/
/**
/**
* Render a list of threads.
* Render a list of threads.
*
*
...
@@ -29,6 +40,8 @@ function getScrollContainer() {
...
@@ -29,6 +40,8 @@ function getScrollContainer() {
* annotations (and replies) are complex interactive components whose
* annotations (and replies) are complex interactive components whose
* user-defined content may include rich media such as images, audio clips,
* user-defined content may include rich media such as images, audio clips,
* embedded YouTube videos, rendered math and more.
* embedded YouTube videos, rendered math and more.
*
* @param {ThreadListProps} props
*/
*/
function
ThreadList
({
thread
})
{
function
ThreadList
({
thread
})
{
const
clearSelection
=
useStore
(
store
=>
store
.
clearSelection
);
const
clearSelection
=
useStore
(
store
=>
store
.
clearSelection
);
...
@@ -51,7 +64,9 @@ function ThreadList({ thread }) {
...
@@ -51,7 +64,9 @@ function ThreadList({ thread }) {
// ID of thread to scroll to after the next render. If the thread is not
// ID of thread to scroll to after the next render. If the thread is not
// present, the value persists until it can be "consumed".
// present, the value persists until it can be "consumed".
const
[
scrollToId
,
setScrollToId
]
=
useState
(
null
);
const
[
scrollToId
,
setScrollToId
]
=
useState
(
/** @type {string|null} */
(
null
)
);
const
topLevelThreads
=
thread
.
children
;
const
topLevelThreads
=
thread
.
children
;
...
@@ -159,7 +174,9 @@ function ThreadList({ thread }) {
...
@@ -159,7 +174,9 @@ function ThreadList({ thread }) {
setThreadHeights
(
prevHeights
=>
{
setThreadHeights
(
prevHeights
=>
{
const
changedHeights
=
{};
const
changedHeights
=
{};
for
(
let
{
id
}
of
visibleThreads
)
{
for
(
let
{
id
}
of
visibleThreads
)
{
const
threadElement
=
document
.
getElementById
(
id
);
const
threadElement
=
/** @type {HTMLElement} */
(
document
.
getElementById
(
id
));
const
height
=
getElementHeightWithMargins
(
threadElement
);
const
height
=
getElementHeightWithMargins
(
threadElement
);
if
(
height
!==
prevHeights
[
id
])
{
if
(
height
!==
prevHeights
[
id
])
{
changedHeights
[
id
]
=
height
;
changedHeights
[
id
]
=
height
;
...
...
src/sidebar/components/thread.js
View file @
96081234
...
@@ -10,10 +10,21 @@ import Annotation from './annotation';
...
@@ -10,10 +10,21 @@ import Annotation from './annotation';
import
Button
from
'./button'
;
import
Button
from
'./button'
;
import
ModerationBanner
from
'./moderation-banner'
;
import
ModerationBanner
from
'./moderation-banner'
;
/** @typedef {import('../util/build-thread').Thread} Thread */
/**
* @typedef ThreadProps
* @prop {boolean} [showDocumentInfo]
* @prop {Thread} thread
* @prop {Object} threadsService - Injected service
*/
/**
/**
* A thread, which contains a single annotation at its top level, and its
* A thread, which contains a single annotation at its top level, and its
* recursively-rendered children (i.e. replies). A thread may have a parent,
* recursively-rendered children (i.e. replies). A thread may have a parent,
* and at any given time it may be `collapsed`.
* and at any given time it may be `collapsed`.
*
* @param {ThreadProps} props
*/
*/
function
Thread
({
showDocumentInfo
=
false
,
thread
,
threadsService
})
{
function
Thread
({
showDocumentInfo
=
false
,
thread
,
threadsService
})
{
const
setExpanded
=
useStore
(
store
=>
store
.
setExpanded
);
const
setExpanded
=
useStore
(
store
=>
store
.
setExpanded
);
...
...
src/sidebar/components/timestamp.js
View file @
96081234
...
@@ -5,9 +5,18 @@ import propTypes from 'prop-types';
...
@@ -5,9 +5,18 @@ import propTypes from 'prop-types';
import
{
format
as
formatDate
}
from
'../util/date'
;
import
{
format
as
formatDate
}
from
'../util/date'
;
import
{
decayingInterval
,
toFuzzyString
}
from
'../util/time'
;
import
{
decayingInterval
,
toFuzzyString
}
from
'../util/time'
;
/**
* @typedef TimestampProps
* @prop {string} [className] - Custom class name for the anchor/span element
* @prop {string} [href] - Link destination
* @prop {string} timestamp - The timestamp as an ISO 8601 date string
*/
/**
/**
* Display a relative timestamp (eg. '6 minutes ago') as static text or a link.
* Display a relative timestamp (eg. '6 minutes ago') as static text or a link.
*
*
* @param {TimestampProps} props
*
* The timestamp automatically refreshes at an appropriate frequency.
* The timestamp automatically refreshes at an appropriate frequency.
*/
*/
export
default
function
Timestamp
({
className
,
href
,
timestamp
})
{
export
default
function
Timestamp
({
className
,
href
,
timestamp
})
{
...
@@ -49,14 +58,7 @@ export default function Timestamp({ className, href, timestamp }) {
...
@@ -49,14 +58,7 @@ export default function Timestamp({ className, href, timestamp }) {
}
}
Timestamp
.
propTypes
=
{
Timestamp
.
propTypes
=
{
/** Custom class name for the anchor/span element. */
className
:
propTypes
.
string
,
className
:
propTypes
.
string
,
/** Link destination. */
href
:
propTypes
.
string
,
href
:
propTypes
.
string
,
/**
* The timestamp as an ISO 8601 date string.
*/
timestamp
:
propTypes
.
string
.
isRequired
,
timestamp
:
propTypes
.
string
.
isRequired
,
};
};
src/sidebar/store/modules/annotations.js
View file @
96081234
...
@@ -564,6 +564,7 @@ function savedAnnotations(state) {
...
@@ -564,6 +564,7 @@ function savedAnnotations(state) {
* @prop {(tags: string[]) => string[]} findIDsForTags
* @prop {(tags: string[]) => string[]} findIDsForTags
* @prop {() => string[]} focusedAnnotations
* @prop {() => string[]} focusedAnnotations
* @prop {() => string[]} highlightedAnnotations
* @prop {() => string[]} highlightedAnnotations
* @prop {(tag: string) => boolean} isAnnotationFocused
* @prop {() => boolean} isWaitingToAnchorAnnotations
* @prop {() => boolean} isWaitingToAnchorAnnotations
* @prop {() => Annotation[]} newAnnotations
* @prop {() => Annotation[]} newAnnotations
* @prop {() => Annotation[]} newHighlights
* @prop {() => Annotation[]} newHighlights
...
...
src/tsconfig.json
View file @
96081234
...
@@ -35,9 +35,6 @@
...
@@ -35,9 +35,6 @@
"sidebar/components/sidebar-content-error.js"
,
"sidebar/components/sidebar-content-error.js"
,
"sidebar/components/sidebar-content.js"
,
"sidebar/components/sidebar-content.js"
,
"sidebar/components/stream-content.js"
,
"sidebar/components/stream-content.js"
,
"sidebar/components/thread-card.js"
,
"sidebar/components/thread-list.js"
,
"sidebar/components/thread.js"
,
"sidebar/components/top-bar.js"
"sidebar/components/top-bar.js"
]
]
}
}
src/types/api.js
View file @
96081234
...
@@ -116,7 +116,7 @@
...
@@ -116,7 +116,7 @@
* we assign a default organization on the client.
* we assign a default organization on the client.
* @prop {GroupScopes|null} scopes
* @prop {GroupScopes|null} scopes
* @prop {Object} links
* @prop {Object} links
* @prop {string}
links.html
* @prop {string}
[links.html]
*
*
* // Properties not present on API objects, but added by utilities in the client.
* // Properties not present on API objects, but added by utilities in the client.
* @prop {string} logo
* @prop {string} logo
...
...
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