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
eed9efb0
Commit
eed9efb0
authored
Jul 28, 2022
by
Lyza Danger Gardner
Committed by
Lyza Gardner
Aug 02, 2022
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Implement content banner layout
parent
dfb7db12
Changes
6
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
206 additions
and
73 deletions
+206
-73
jstor.config.json
dev-server/documents/pdf/jstor.config.json
+2
-2
ContentInfoBanner.js
src/annotator/components/ContentInfoBanner.js
+102
-25
ContentInfoBanner-test.js
src/annotator/components/test/ContentInfoBanner-test.js
+99
-29
pdf.js
src/annotator/integrations/pdf.js
+1
-4
pdf-test.js
src/annotator/integrations/test/pdf-test.js
+0
-13
tailwind.config.mjs
tailwind.config.mjs
+2
-0
No files found.
dev-server/documents/pdf/jstor.config.json
View file @
eed9efb0
...
...
@@ -7,8 +7,8 @@
"title"
:
"Document provided by JSTOR"
},
"item"
:
{
"title"
:
"
Chapter 2: A chapter
"
,
"containerTitle"
:
"
Book Title Here
"
"title"
:
"
Populations of Thrips tabaci, with Special Reference to Virus Transmission
"
,
"containerTitle"
:
"
Journal of Animal Ecology
"
},
"links"
:
{
"previousItem"
:
"https://jstor.org/stable/book123.1"
,
...
...
src/annotator/components/ContentInfoBanner.js
View file @
eed9efb0
import
{
LabeledButton
,
Link
}
from
'@hypothesis/frontend-shared'
;
import
classnames
from
'classnames'
;
import
{
Link
,
CaretLeftIcon
,
CaretRightIcon
,
}
from
'@hypothesis/frontend-shared/lib/next'
;
/**
* @typedef {import('../../types/annotator').ContentInfoConfig} ContentInfoConfig
...
...
@@ -8,40 +14,111 @@ import { LabeledButton, Link } from '@hypothesis/frontend-shared';
* A banner that displays information about the current document and the entity
* that is providing access to it (eg. JSTOR).
*
* Layout columns:
* - Logo
* - Container title (only shown on screens at `2xl` breakpoint and wider)
* - Item title with previous and next links
*
* @param {object} props
* @param {ContentInfoConfig} props.info
* @param {() => void} props.onClose
*/
export
default
function
ContentInfoBanner
({
info
,
onClose
})
{
export
default
function
ContentInfoBanner
({
info
})
{
return
(
<
div
className
=
"flex items-center border-b gap-x-4 px-2 py-1 bg-white text-annotator-lg"
>
<
Link
href
=
{
info
.
logo
.
link
}
target
=
"_blank"
data
-
testid
=
"logo-link"
>
<
img
src
=
{
info
.
logo
.
logo
}
alt
=
{
info
.
logo
.
title
}
/
>
<
/Link
>
<
div
className
=
"grow"
>
{
info
.
links
.
previousItem
&&
(
<
Link
href
=
{
info
.
links
.
previousItem
}
target
=
"_blank"
>
Previous
article
<
div
className
=
{
classnames
(
'h-10 bg-white px-4 text-slate-7 text-annotator-base border-b'
,
'grid items-center'
,
// Two columns in narrower viewports; three in wider
'grid-cols-[100px_minmax(0,auto)] gap-x-4'
,
'2xl:grid-cols-[100px_minmax(0,auto)_minmax(0,auto)] 2xl:gap-x-3'
)}
>
<
div
data
-
testid
=
"content-logo"
>
{
info
.
logo
&&
(
<
Link
href
=
{
info
.
logo
.
link
}
target
=
"_blank"
data
-
testid
=
"logo-link"
>
<
img
alt
=
{
info
.
logo
.
title
}
src
=
{
info
.
logo
.
logo
}
data
-
testid
=
"logo-image"
/>
<
/Link
>
)}
{
info
.
item
.
title
}
{
info
.
item
.
containerTitle
&&
(
<
span
>
{
' '
}
from
<
i
>
{
info
.
item
.
containerTitle
}
<
/i
>
<
/span
>
<
/div
>
<
div
className
=
{
classnames
(
// Container title (this element) is not shown on narrow screens
'hidden'
,
'2xl:block 2xl:whitespace-nowrap 2xl:overflow-hidden 2xl:text-ellipsis'
,
'font-semibold'
)}
data
-
testid
=
"content-container-info"
title
=
{
info
.
item
.
containerTitle
}
>
{
info
.
item
.
containerTitle
}
<
/div
>
<
div
className
=
{
classnames
(
// Flex layout for item title, next and previous links
'flex justify-center items-center gap-x-2'
)}
data
-
testid
=
"content-item-info"
>
<
div
className
=
{
classnames
(
// Narrower viewports center this flex content:
// this element is not needed for alignment
'hidden'
,
// Wider viewports align this flex content to the right:
// This empty element is needed to fill extra space at left
'2xl:block 2xl:grow'
)}
/
>
{
info
.
links
.
previousItem
&&
(
<>
<
Link
classes
=
"flex gap-x-1 items-center text-annotator-sm whitespace-nowrap"
title
=
"Open previous item"
href
=
{
info
.
links
.
previousItem
}
underline
=
"always"
target
=
"_blank"
data
-
testid
=
"content-previous-link"
>
<
CaretLeftIcon
className
=
"w-em h-em"
/>
<
span
>
Previous
<
/span
>
<
/Link
>
<
div
className
=
"text-annotator-sm"
>|<
/div
>
<
/
>
)}
<
div
className
=
{
classnames
(
// This element will shrink and truncate fluidly.
// Overriding min-width `auto` prevents the content from overflowing
// See https://stackoverflow.com/a/66689926/434243.
'min-w-0 whitespace-nowrap overflow-hidden text-ellipsis shrink font-medium'
)}
>
<
span
title
=
{
info
.
item
.
title
}
data
-
testid
=
"content-item-title"
>
{
info
.
item
.
title
}
<
/span
>
<
/div
>
{
info
.
links
.
nextItem
&&
(
<
Link
href
=
{
info
.
links
.
nextItem
}
target
=
"_blank"
>
Next
article
<
/Link
>
<>
<
div
className
=
"text-annotator-sm"
>|<
/div
>
<
Link
title
=
"Open next item"
classes
=
"flex gap-x-1 items-center text-annotator-sm whitespace-nowrap"
href
=
{
info
.
links
.
nextItem
}
underline
=
"always"
target
=
"_blank"
data
-
testid
=
"content-next-link"
>
<
span
>
Next
<
/span
>
<
CaretRightIcon
className
=
"w-em h-em"
/>
<
/Link
>
<
/
>
)}
<
/div
>
<
div
className
=
"text-annotator-base"
>
<
LabeledButton
onClick
=
{
onClose
}
data
-
testid
=
"close-button"
>
Close
<
/LabeledButton
>
<
/div
>
<
/div
>
);
}
src/annotator/components/test/ContentInfoBanner-test.js
View file @
eed9efb0
...
...
@@ -2,43 +2,113 @@ import { mount } from 'enzyme';
import
ContentInfoBanner
from
'../ContentInfoBanner'
;
const
contentInfo
=
{
logo
:
{
link
:
'https://www.jstor.org'
,
logo
:
'https://www.jstorg.org/logo.svg'
,
title
:
'JSTOR homepage'
,
},
item
:
{
title
:
'Chapter 2: Some book chapter'
,
},
links
:
{
previousItem
:
'https://www.jstor.org/stable/book.123.1'
,
nextItem
:
'https://www.jstor.org/stable/book.123.3'
,
},
};
let
contentInfo
;
const
createComponent
=
props
=>
mount
(
<
ContentInfoBanner
info
=
{
contentInfo
}
{...
props
}
/>
)
;
describe
(
'ContentInfoBanner'
,
()
=>
{
it
(
'renders banner'
,
()
=>
{
const
wrapper
=
mount
(
<
ContentInfoBanner
info
=
{
contentInfo
}
/>
)
;
assert
.
include
(
wrapper
.
text
(),
contentInfo
.
item
.
title
);
beforeEach
(()
=>
{
contentInfo
=
{
logo
:
{
link
:
'https://www.jstor.org'
,
logo
:
'https://www.jstor.org/logo.svg'
,
title
:
'JSTOR homepage'
,
},
item
:
{
title
:
'Chapter 2: Some book chapter'
,
containerTitle
:
'Expansive Book'
,
},
links
:
{
previousItem
:
'https://www.jstor.org/stable/book.123.1'
,
nextItem
:
'https://www.jstor.org/stable/book.123.3'
,
},
};
});
it
(
'shows linked partner logo'
,
()
=>
{
const
wrapper
=
createComponent
();
const
logo
=
wrapper
.
find
(
'Link[data-testid="logo-link"]'
);
assert
.
equal
(
logo
.
prop
(
'href'
),
contentInfo
.
logo
.
link
);
const
logoLink
=
wrapper
.
find
(
'Link[data-testid="logo-link"]'
);
const
logoImg
=
wrapper
.
find
(
'img[data-testid="logo-image"]'
);
assert
.
equal
(
logoLink
.
prop
(
'href'
),
contentInfo
.
logo
.
link
);
assert
.
equal
(
logoImg
.
prop
(
'src'
),
'https://www.jstor.org/logo.svg'
);
});
it
(
'closes when "Close" button is clicked'
,
()
=>
{
const
onClose
=
sinon
.
stub
();
const
wrapper
=
mount
(
<
ContentInfoBanner
info
=
{
contentInfo
}
onClose
=
{
onClose
}
/
>
it
(
'shows item title'
,
()
=>
{
const
wrapper
=
createComponent
();
// TODO: This should be a link once the item link is available in
// content-info metadata
const
title
=
wrapper
.
find
(
'span[data-testid="content-item-title"]'
);
assert
.
equal
(
title
.
text
(),
'Chapter 2: Some book chapter'
);
});
it
(
'provides disclosure of long titles through title attributes'
,
()
=>
{
const
wrapper
=
createComponent
();
// Element text could be partially obscured (CSS truncation), so these
// title attributes provide access to the full titles
assert
.
equal
(
wrapper
.
find
(
'div[data-testid="content-container-info"]'
).
prop
(
'title'
),
'Expansive Book'
);
const
closeButton
=
wrapper
.
find
(
'LabeledButton[data-testid="close-button"]'
assert
.
equal
(
wrapper
.
find
(
'span[data-testid="content-item-title"]'
).
prop
(
'title'
),
'Chapter 2: Some book chapter'
);
});
describe
(
'next and previous links'
,
()
=>
{
it
(
'displays next and previous links when available'
,
()
=>
{
const
wrapper
=
createComponent
();
const
prevLink
=
wrapper
.
find
(
'Link[data-testid="content-previous-link"]'
);
const
nextLink
=
wrapper
.
find
(
'Link[data-testid="content-next-link"]'
);
assert
.
equal
(
prevLink
.
prop
(
'href'
),
'https://www.jstor.org/stable/book.123.1'
);
assert
.
equal
(
nextLink
.
prop
(
'href'
),
'https://www.jstor.org/stable/book.123.3'
);
});
it
(
'does not display previous link if unavailable'
,
()
=>
{
const
noLinks
=
{
...
contentInfo
};
delete
noLinks
.
links
.
previousItem
;
const
wrapper
=
createComponent
({
contentInfo
:
noLinks
});
const
prevLink
=
wrapper
.
find
(
'Link[data-testid="content-previous-link"]'
);
const
nextLink
=
wrapper
.
find
(
'Link[data-testid="content-next-link"]'
);
assert
.
isFalse
(
prevLink
.
exists
());
assert
.
isTrue
(
nextLink
.
exists
());
});
it
(
'does not display next link if unavailable'
,
()
=>
{
const
noLinks
=
{
...
contentInfo
};
delete
noLinks
.
links
.
nextItem
;
const
wrapper
=
createComponent
({
contentInfo
:
noLinks
});
closeButton
.
prop
(
'onClick'
)();
const
prevLink
=
wrapper
.
find
(
'Link[data-testid="content-previous-link"]'
);
const
nextLink
=
wrapper
.
find
(
'Link[data-testid="content-next-link"]'
);
assert
.
calledOnce
(
onClose
);
assert
.
isTrue
(
prevLink
.
exists
());
assert
.
isFalse
(
nextLink
.
exists
());
});
});
});
src/annotator/integrations/pdf.js
View file @
eed9efb0
...
...
@@ -278,10 +278,7 @@ export class PDFIntegration extends TinyEmitter {
render
(
<
Banners
>
{
this
.
_bannerState
.
contentInfo
&&
(
<
ContentInfoBanner
info
=
{
this
.
_bannerState
.
contentInfo
}
onClose
=
{()
=>
this
.
_updateBannerState
({
contentInfo
:
null
})}
/
>
<
ContentInfoBanner
info
=
{
this
.
_bannerState
.
contentInfo
}
/
>
)}
{
this
.
_bannerState
.
noTextWarning
&&
<
WarningBanner
/>
}
<
/Banners>
,
...
...
src/annotator/integrations/test/pdf-test.js
View file @
eed9efb0
...
...
@@ -249,19 +249,6 @@ describe('annotator/integrations/pdf', () => {
assert
.
isNotNull
(
banner
);
assert
.
include
(
banner
.
shadowRoot
.
textContent
,
contentInfo
.
item
.
title
);
});
it
(
'closes content info banner when "Close" button is clicked'
,
()
=>
{
pdfIntegration
=
createPDFIntegration
();
pdfIntegration
.
showContentInfo
(
contentInfo
);
const
banner
=
getBanner
();
const
closeButton
=
banner
.
shadowRoot
.
querySelector
(
'button[data-testid=close-button]'
);
closeButton
.
click
();
assert
.
isNull
(
getBanner
());
});
});
it
(
'does not show a warning when PDF has selectable text'
,
async
()
=>
{
...
...
tailwind.config.mjs
View file @
eed9efb0
...
...
@@ -170,6 +170,8 @@ export default {
sm
:
'360px'
,
md
:
'480px'
,
lg
:
'768px'
,
xl
:
'1024px'
,
'2xl'
:
'1220px'
,
// Narrow mobile screens
'annotator-sm'
:
'240px'
,
// Wider mobile screens/small tablets
...
...
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