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
7032092c
Unverified
Commit
7032092c
authored
Apr 02, 2020
by
Lyza Gardner
Committed by
GitHub
Apr 02, 2020
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #1988 from hypothesis/annotation-layout
Fix annotation and thread vertical rhythm and layout
parents
c5642ed2
96d9ff5e
Changes
13
Hide whitespace changes
Inline
Side-by-side
Showing
13 changed files
with
214 additions
and
193 deletions
+214
-193
annotation-body.js
src/sidebar/components/annotation-body.js
+44
-30
annotation.js
src/sidebar/components/annotation.js
+44
-36
annotation-body-test.js
src/sidebar/components/test/annotation-body-test.js
+36
-3
annotation-test.js
src/sidebar/components/test/annotation-test.js
+56
-52
annotation-body.scss
src/styles/sidebar/components/annotation-body.scss
+3
-8
annotation-header.scss
src/styles/sidebar/components/annotation-header.scss
+0
-3
annotation-license.scss
src/styles/sidebar/components/annotation-license.scss
+1
-1
annotation-quote.scss
src/styles/sidebar/components/annotation-quote.scss
+1
-6
annotation.scss
src/styles/sidebar/components/annotation.scss
+1
-36
tag-editor.scss
src/styles/sidebar/components/tag-editor.scss
+5
-2
tag-list.scss
src/styles/sidebar/components/tag-list.scss
+7
-7
thread-list.scss
src/styles/sidebar/components/thread-list.scss
+1
-1
thread.scss
src/styles/sidebar/components/thread.scss
+15
-8
No files found.
src/sidebar/components/annotation-body.js
View file @
7032092c
import
{
Fragment
,
createElement
}
from
'preact'
;
import
{
createElement
}
from
'preact'
;
import
{
useState
}
from
'preact/hooks'
;
import
propTypes
from
'prop-types'
;
...
...
@@ -8,6 +8,8 @@ import Button from './button';
import
Excerpt
from
'./excerpt'
;
import
MarkdownEditor
from
'./markdown-editor'
;
import
MarkdownView
from
'./markdown-view'
;
import
TagEditor
from
'./tag-editor'
;
import
TagList
from
'./tag-list'
;
/**
* Display the rendered content of an annotation.
...
...
@@ -15,7 +17,9 @@ import MarkdownView from './markdown-view';
export
default
function
AnnotationBody
({
annotation
,
isEditing
,
onEditTags
,
onEditText
,
tags
,
text
,
})
{
// Should the text content of `Excerpt` be rendered in a collapsed state,
...
...
@@ -31,36 +35,37 @@ export default function AnnotationBody({
?
'Show full annotation text'
:
'Show the first few lines only'
;
const
showExcerpt
=
!
isEditing
&&
text
.
length
>
0
;
const
showTagList
=
!
isEditing
&&
tags
.
length
>
0
;
return
(
<
Fragment
>
<
section
className
=
"annotation-body"
>
{
!
isEditing
&&
(
<
Excerpt
collapse
=
{
isCollapsed
}
collapsedHeight
=
{
400
}
inlineControls
=
{
false
}
onCollapsibleChanged
=
{
setIsCollapsible
}
onToggleCollapsed
=
{
setIsCollapsed
}
overflowThreshold
=
{
20
}
>
<
MarkdownView
markdown
=
{
text
}
textClass
=
{{
'annotation-body__text'
:
true
,
'is-hidden'
:
isHidden
(
annotation
),
'has-content'
:
text
.
length
>
0
,
}}
/
>
<
/Excerpt
>
)}
{
isEditing
&&
(
<
MarkdownEditor
label
=
"Annotation body"
text
=
{
text
}
onEditText
=
{
onEditText
}
<
section
className
=
"annotation-body"
>
{
showExcerpt
&&
(
<
Excerpt
collapse
=
{
isCollapsed
}
collapsedHeight
=
{
400
}
inlineControls
=
{
false
}
onCollapsibleChanged
=
{
setIsCollapsible
}
onToggleCollapsed
=
{
setIsCollapsed
}
overflowThreshold
=
{
20
}
>
<
MarkdownView
markdown
=
{
text
}
textClass
=
{{
'annotation-body__text'
:
true
,
'is-hidden'
:
isHidden
(
annotation
),
'has-content'
:
text
.
length
>
0
,
}}
/
>
)}
<
/section
>
<
/Excerpt
>
)}
{
isEditing
&&
(
<
MarkdownEditor
label
=
"Annotation body"
text
=
{
text
}
onEditText
=
{
onEditText
}
/
>
)}
{
isCollapsible
&&
!
isEditing
&&
(
<
div
className
=
"annotation-body__collapse-toggle"
>
<
Button
...
...
@@ -71,7 +76,9 @@ export default function AnnotationBody({
/
>
<
/div
>
)}
<
/Fragment
>
{
showTagList
&&
<
TagList
annotation
=
{
annotation
}
tags
=
{
tags
}
/>
}
{
isEditing
&&
<
TagEditor
onEditTags
=
{
onEditTags
}
tagList
=
{
tags
}
/>
}
<
/section
>
);
}
...
...
@@ -86,11 +93,18 @@ AnnotationBody.propTypes = {
*/
isEditing
:
propTypes
.
bool
,
/**
* Callback invoked when the user edits tags.
*/
onEditTags
:
propTypes
.
func
,
/**
* Callback invoked when the user edits the content of the annotation body.
*/
onEditText
:
propTypes
.
func
,
tags
:
propTypes
.
array
.
isRequired
,
/**
* The markdown annotation body, which is either rendered as HTML (if `isEditing`
* is false) or displayed in a text area otherwise.
...
...
src/sidebar/components/annotation.js
View file @
7032092c
...
...
@@ -15,8 +15,6 @@ import AnnotationLicense from './annotation-license';
import
AnnotationPublishControl
from
'./annotation-publish-control'
;
import
AnnotationQuote
from
'./annotation-quote'
;
import
Button
from
'./button'
;
import
TagEditor
from
'./tag-editor'
;
import
TagList
from
'./tag-list'
;
/**
* A single annotation.
...
...
@@ -52,7 +50,7 @@ function Annotation({
const
toggleAction
=
threadIsCollapsed
?
'Show replies'
:
'Hide replies'
;
const
toggleText
=
`
${
toggleAction
}
(
${
replyCount
}
)`
;
const
shouldShowActions
=
!
isSaving
&&
!
isEditing
&&
!
isCollapsedReply
;
const
shouldShowActions
=
!
isSaving
&&
!
isEditing
;
const
shouldShowLicense
=
isEditing
&&
!
isPrivate
&&
group
.
type
!==
'private'
;
const
shouldShowReplyToggle
=
replyCount
>
0
&&
!
isReply
(
annotation
);
...
...
@@ -108,42 +106,52 @@ function Annotation({
showDocumentInfo
=
{
showDocumentInfo
}
threadIsCollapsed
=
{
threadIsCollapsed
}
/
>
{
hasQuote
&&
<
AnnotationQuote
annotation
=
{
annotation
}
/>
}
<
AnnotationBody
annotation
=
{
annotation
}
isEditing
=
{
isEditing
}
onEditText
=
{
onEditText
}
text
=
{
text
}
/
>
{
isEditing
&&
<
TagEditor
onEditTags
=
{
onEditTags
}
tagList
=
{
tags
}
/>
}
{
!
isEditing
&&
<
TagList
annotation
=
{
annotation
}
tags
=
{
tags
}
/>
}
<
footer
className
=
"annotation__footer"
>
{
isEditing
&&
(
<
div
className
=
"annotation__form-actions"
>
<
AnnotationPublishControl
annotation
=
{
annotation
}
isDisabled
=
{
isEmpty
}
onSave
=
{
onSave
}
/
>
<
/div
>
)}
{
shouldShowLicense
&&
<
AnnotationLicense
/>
}
<
div
className
=
"annotation__controls"
>
{
shouldShowReplyToggle
&&
(
<
Button
className
=
"annotation__reply-toggle"
onClick
=
{
onToggleReplies
}
buttonText
=
{
toggleText
}
/
>
)}
{
isSaving
&&
<
div
className
=
"annotation__actions"
>
Saving
...
<
/div>
}
{
shouldShowActions
&&
(
<
div
className
=
"annotation__actions"
>
<
AnnotationActionBar
annotation
=
{
annotation
}
onReply
=
{
onReply
}
/
>
{
!
isCollapsedReply
&&
(
<
AnnotationBody
annotation
=
{
annotation
}
isEditing
=
{
isEditing
}
onEditTags
=
{
onEditTags
}
onEditText
=
{
onEditText
}
tags
=
{
tags
}
text
=
{
text
}
/
>
)}
{
!
isCollapsedReply
&&
(
<
footer
className
=
"annotation__footer"
>
{
isEditing
&&
(
<
div
className
=
"annotation__form-actions"
>
<
AnnotationPublishControl
annotation
=
{
annotation
}
isDisabled
=
{
isEmpty
}
onSave
=
{
onSave
}
/
>
<
/div
>
)}
<
/div
>
<
/footer
>
{
shouldShowLicense
&&
<
AnnotationLicense
/>
}
<
div
className
=
"annotation__controls"
>
{
shouldShowReplyToggle
&&
(
<
Button
className
=
"annotation__reply-toggle"
onClick
=
{
onToggleReplies
}
buttonText
=
{
toggleText
}
/
>
)}
{
isSaving
&&
<
div
className
=
"annotation__actions"
>
Saving
...
<
/div>
}
{
shouldShowActions
&&
(
<
div
className
=
"annotation__actions"
>
<
AnnotationActionBar
annotation
=
{
annotation
}
onReply
=
{
onReply
}
/
>
<
/div
>
)}
<
/div
>
<
/footer
>
)}
<
/article
>
);
}
...
...
src/sidebar/components/test/annotation-body-test.js
View file @
7032092c
...
...
@@ -16,6 +16,8 @@ describe('AnnotationBody', () => {
<
AnnotationBody
annotation
=
{
fixtures
.
defaultAnnotation
()}
isEditing
=
{
false
}
onEditTags
=
{()
=>
null
}
tags
=
{[]}
text
=
"test comment"
{...
props
}
/
>
...
...
@@ -88,10 +90,41 @@ describe('AnnotationBody', () => {
assert
.
equal
(
buttonProps
.
title
,
'Show the first few lines only'
);
});
describe
(
'tag list and editor'
,
()
=>
{
it
(
'renders a list of tags if not editing and annotation has tags'
,
()
=>
{
const
wrapper
=
createBody
({
isEditing
:
false
,
tags
:
[
'foo'
,
'bar'
]
});
assert
.
isTrue
(
wrapper
.
find
(
'TagList'
).
exists
());
});
it
(
'does not render a tag list if annotation has no tags'
,
()
=>
{
const
wrapper
=
createBody
({
isEditing
:
false
,
tags
:
[]
});
assert
.
isFalse
(
wrapper
.
find
(
'TagList'
).
exists
());
});
it
(
'renders a tag editor if annotation is being edited'
,
()
=>
{
const
wrapper
=
createBody
({
isEditing
:
true
,
tags
:
[
'foo'
,
'bar'
]
});
assert
.
isTrue
(
wrapper
.
find
(
'TagEditor'
).
exists
());
assert
.
isFalse
(
wrapper
.
find
(
'TagList'
).
exists
());
});
});
it
(
'should pass a11y checks'
,
checkAccessibility
({
content
:
()
=>
createBody
(),
})
checkAccessibility
([
{
content
:
()
=>
createBody
(),
},
{
name
:
'when annotation has tags (tag list)'
,
content
:
()
=>
createBody
({
isEditing
:
false
,
tags
:
[
'foo'
,
'bar'
]
}),
},
{
name
:
'when annotation is being edited and has tags'
,
content
:
()
=>
createBody
({
isEditing
:
true
,
tags
:
[
'foo'
,
'bar'
]
}),
},
])
);
});
src/sidebar/components/test/annotation-test.js
View file @
7032092c
...
...
@@ -8,8 +8,6 @@ import { checkAccessibility } from '../../../test-util/accessibility';
import
mockImportedComponents
from
'../../../test-util/mock-imported-components'
;
import
{
waitFor
}
from
'../../../test-util/wait'
;
// @TODO Note this import as `Annotation` for easier updating later
import
Annotation
from
'../annotation'
;
import
{
$imports
}
from
'../annotation'
;
...
...
@@ -145,38 +143,6 @@ describe('Annotation', () => {
});
});
describe
(
'tags'
,
()
=>
{
it
(
'renders tag editor if `isEditing'
,
()
=>
{
setEditingMode
(
true
);
const
wrapper
=
createComponent
();
assert
.
isTrue
(
wrapper
.
find
(
'TagEditor'
).
exists
());
assert
.
isFalse
(
wrapper
.
find
(
'TagList'
).
exists
());
});
it
(
'updates annotation draft if tags changed'
,
()
=>
{
setEditingMode
(
true
);
const
wrapper
=
createComponent
();
wrapper
.
find
(
'TagEditor'
)
.
props
()
.
onEditTags
({
tags
:
[
'uno'
,
'dos'
]
});
const
call
=
fakeStore
.
createDraft
.
getCall
(
0
);
assert
.
calledOnce
(
fakeStore
.
createDraft
);
assert
.
sameMembers
(
call
.
args
[
1
].
tags
,
[
'uno'
,
'dos'
]);
});
it
(
'renders tag list if not `isEditing'
,
()
=>
{
const
wrapper
=
createComponent
();
assert
.
isTrue
(
wrapper
.
find
(
'TagList'
).
exists
());
assert
.
isFalse
(
wrapper
.
find
(
'TagEditor'
).
exists
());
});
});
describe
(
'publish control'
,
()
=>
{
it
(
'should show the publish control if in edit mode'
,
()
=>
{
setEditingMode
(
true
);
...
...
@@ -461,28 +427,66 @@ describe('Annotation', () => {
assert
.
isFalse
(
wrapper
.
find
(
'AnnotationActionBar'
).
exists
());
});
});
it
(
'should not show annotations if the annotation is a collapsed reply'
,
()
=>
{
fakeMetadata
.
isReply
.
returns
(
true
);
const
wrapper
=
createComponent
({
threadIsCollapsed
:
true
});
context
(
'annotation thread is collapsed'
,
()
=>
{
context
(
'collapsed reply'
,
()
=>
{
beforeEach
(()
=>
{
fakeMetadata
.
isReply
.
returns
(
true
);
});
assert
.
isFalse
(
wrapper
.
find
(
'AnnotationActionBar'
).
exists
());
it
(
'should not render body or footer'
,
()
=>
{
const
wrapper
=
createComponent
({
threadIsCollapsed
:
true
});
assert
.
isFalse
(
wrapper
.
find
(
'AnnotationBody'
).
exists
());
assert
.
isFalse
(
wrapper
.
find
(
'footer'
).
exists
());
});
it
(
'should not show actions'
,
()
=>
{
const
wrapper
=
createComponent
({
threadIsCollapsed
:
true
});
assert
.
isFalse
(
wrapper
.
find
(
'AnnotationActionBar'
).
exists
());
});
});
it
(
'should pass a11y checks'
,
checkAccessibility
([
{
content
:
()
=>
createComponent
(),
context
(
'collapsed top-level annotation'
,
()
=>
{
it
(
'should render body and footer'
,
()
=>
{
fakeMetadata
.
isReply
.
returns
(
false
);
const
wrapper
=
createComponent
({
threadIsCollapsed
:
true
});
assert
.
isTrue
(
wrapper
.
find
(
'AnnotationBody'
).
exists
());
assert
.
isTrue
(
wrapper
.
find
(
'footer'
).
exists
());
});
});
});
it
(
'should pass a11y checks'
,
checkAccessibility
([
{
content
:
()
=>
createComponent
(),
},
{
name
:
'When editing'
,
content
:
()
=>
{
setEditingMode
(
true
);
return
createComponent
();
},
{
name
:
'When editing'
,
content
:
()
=>
{
setEditingMode
(
true
);
return
createComponent
(
);
},
},
{
name
:
'when a collapsed top-level thread'
,
content
:
()
=>
{
fakeMetadata
.
isReply
.
returns
(
false
);
return
createComponent
({
threadIsCollapsed
:
true
});
},
])
);
});
},
{
name
:
'when a collapsed reply'
,
content
:
()
=>
{
fakeMetadata
.
isReply
.
returns
(
true
);
return
createComponent
({
threadIsCollapsed
:
true
});
},
},
])
);
});
src/styles/sidebar/components/annotation-body.scss
View file @
7032092c
...
...
@@ -3,12 +3,7 @@
.annotation-body
{
@include
var
.
font-normal
;
color
:
var
.
$grey-7
;
// Margin between top of ascent of annotation body and
// bottom of ascent of annotation-quote should be ~15px.
margin-top
:
var
.
$layout-h-margin
-
5px
;
margin-bottom
:
var
.
$layout-h-margin
;
margin-right
:
0px
;
margin-left
:
0px
;
margin
:
1em
0
;
}
.annotation-body__text
{
...
...
@@ -38,12 +33,12 @@
}
.annotation-body__collapse-toggle
{
// Negative top margin to bring this up tight under `.annotation-body`
margin-top
:
-
(
var
.
$layout-h-margin
-
5px
);
margin
:
0
.5em
0
;
display
:
flex
;
justify-content
:
flex-end
;
.annotation-body__collapse-toggle-button
{
padding
:
0
.5em
;
background-color
:
transparent
;
}
}
src/styles/sidebar/components/annotation-header.scss
View file @
7032092c
...
...
@@ -3,9 +3,6 @@
.annotation-header
{
@include
forms
.
pie-clearfix
;
// Margin between top of x-height of username and
// top of the annotation card should be ~15px
margin-top
:
-
(
var
.
$layout-h-margin
)
+
10px
;
color
:
var
.
$grey-6
;
&
__row
{
...
...
src/styles/sidebar/components/annotation-license.scss
View file @
7032092c
...
...
@@ -4,7 +4,7 @@
@include
var
.
font-small
;
border-top
:
1px
solid
var
.
$grey-3
;
padding-top
:
0
.5em
;
margin
:
0
.5
em
0
;
margin
:
1
em
0
;
&
__link
{
display
:
flex
;
...
...
src/styles/sidebar/components/annotation-quote.scss
View file @
7032092c
@use
'../../variables'
as
var
;
.annotation-quote
{
// Margin between top of ascent of annotation quote and
// bottom of ascent of username should be ~15px
margin-top
:
var
.
$layout-h-margin
-
5px
;
// Margin between bottom of ascent of annotation quote and
// top of ascent of annotation-body should be ~15px
margin-bottom
:
var
.
$layout-h-margin
-
3px
;
margin
:
1em
0
;
}
.annotation-quote.is-orphan
{
...
...
src/styles/sidebar/components/annotation.scss
View file @
7032092c
...
...
@@ -66,42 +66,7 @@
&
__footer
{
@include
var
.
font-normal
;
color
:
var
.
$grey-5
;
margin-top
:
var
.
$layout-h-margin
;
}
&
--reply
&
__footer
{
margin-top
:
var
.
$layout-h-margin
-
8px
;
}
}
// FIXME vertical rhythm here should be refactored
.annotation--reply
{
.annotation-header
{
// Margin between bottom of ascent of annotation card footer labels
// and top of ascent of username should be ~20px
margin-top
:
0px
;
}
.annotation-body
{
// Margin between top of ascent of annotation body and
// bottom of ascent of username should be ~15px
margin-top
:
var
.
$layout-h-margin
-
8px
;
// Margin between bottom of ascent of annotation body and
// top of annotation footer labels should be ~15px
margin-bottom
:
var
.
$layout-h-margin
-
3px
;
}
}
.annotation--reply.is-collapsed
{
margin-bottom
:
0
;
.annotation-header
{
margin
:
0
;
}
.annotation-body
,
.annotation-footer
{
display
:
none
;
margin-top
:
1em
;
}
}
...
...
src/styles/sidebar/components/tag-editor.scss
View file @
7032092c
...
...
@@ -3,6 +3,8 @@
@use
"../../variables"
as
var
;
.tag-editor
{
margin
:
0
.5em
0
;
&
__input
{
@include
forms
.
form-input
;
width
:
100%
;
...
...
@@ -11,12 +13,13 @@
&
__tags
{
display
:
flex
;
flex-wrap
:
wrap
;
margin
:
0
.5em
0
;
}
&
__item
{
display
:
flex
;
margin
-right
:
0
.5em
;
margin-bottom
:
0
.5em
;
margin
:
0
.25em
0
.5em
0
.25em
0
;
line-height
:
var
.
$base-line-height
;
}
&
__edit
{
...
...
src/styles/sidebar/components/tag-list.scss
View file @
7032092c
@use
"../../variables"
as
var
;
.tag-list
{
margin
:
1em
0
;
display
:
flex
;
flex-wrap
:
wrap
;
&
__item
{
display
:
flex
;
margin-right
:
0
.5em
;
margin-bottom
:
0
.5em
;
margin
:
0
.25em
0
.5em
0
.25em
0
;
padding
:
0
0
.5em
;
line-height
:
var
.
$base-line-height
;
background
:
var
.
$grey-1
;
border
:
1px
solid
var
.
$grey-3
;
border-radius
:
2px
;
}
&
__link
,
&
__text
{
text-decoration
:
none
;
color
:
var
.
$grey-6
;
background
:
var
.
$grey-1
;
border
:
1px
solid
var
.
$grey-3
;
border-radius
:
2px
;
padding
:
0
0
.5em
;
}
&
__link
{
...
...
src/styles/sidebar/components/thread-list.scss
View file @
7032092c
...
...
@@ -11,7 +11,7 @@
box-shadow
:
0px
1px
1px
0px
rgba
(
0
,
0
,
0
,
0
.1
);
border-radius
:
2px
;
cursor
:
pointer
;
padding
:
var
.
$layout-h-margin
;
padding
:
1em
;
background-color
:
var
.
$white
;
&
:hover
{
...
...
src/styles/sidebar/components/thread.scss
View file @
7032092c
...
...
@@ -5,28 +5,35 @@
&
--reply
{
margin-top
:
0
.5em
;
padding-top
:
0
.5em
;
}
// Conserve space for deeper usable nesting
&
__children
{
margin-left
:
-0
.75em
;
}
// Left "channel" of thread
&
__collapse
{
margin
:
0
.25em
;
margin-top
:
0
;
margin-right
:
1em
;
border-right
:
1px
dashed
var
.
$grey-3
;
// The entire channel is NOT clickable so don't make it look like it is
// (overrides `pointer` cursor applied to entire card)
cursor
:
auto
;
border-left
:
1px
dashed
var
.
$grey-3
;
// Darken thread line on hover as a visual cue to show related thread items
&
:hover
{
border-
lef
t
:
1px
dashed
var
.
$grey-4
;
border-
righ
t
:
1px
dashed
var
.
$grey-4
;
}
.is-collapsed
&
{
border-
lef
t
:
none
;
border-
righ
t
:
none
;
}
}
// TODO These styles should be consolidated with other `Button` styles
&
__collapse-button
{
margin-
lef
t
:
-1
.25em
;
padding
:
0
.25em
0
.75em
1
em
0
.75em
;
margin-
righ
t
:
-1
.25em
;
padding
:
0
.25em
0
.75em
0
.75
em
0
.75em
;
// Need a non-transparent background so that the dashed border line
// does not show through the button
background-color
:
var
.
$white
;
...
...
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