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
cef29a96
Commit
cef29a96
authored
Aug 24, 2023
by
Alejandro Celaya
Committed by
Alejandro Celaya
Aug 24, 2023
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Make ShareDialog receive the tabs to render
parent
3ea79419
Changes
6
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
105 additions
and
67 deletions
+105
-67
HypothesisApp.tsx
src/sidebar/components/HypothesisApp.tsx
+15
-1
ShareDialog.tsx
src/sidebar/components/ShareDialog/ShareDialog.tsx
+35
-22
ShareDialog-test.js
src/sidebar/components/ShareDialog/test/ShareDialog-test.js
+24
-22
TopBar.tsx
src/sidebar/components/TopBar.tsx
+4
-3
HypothesisApp-test.js
src/sidebar/components/test/HypothesisApp-test.js
+24
-0
TopBar-test.js
src/sidebar/components/test/TopBar-test.js
+3
-19
No files found.
src/sidebar/components/HypothesisApp.tsx
View file @
cef29a96
...
...
@@ -4,6 +4,7 @@ import { useEffect, useMemo } from 'preact/hooks';
import
{
confirm
}
from
'../../shared/prompts'
;
import
type
{
SidebarSettings
}
from
'../../types/config'
;
import
{
serviceConfig
}
from
'../config/service-config'
;
import
{
isThirdPartyService
}
from
'../helpers/is-third-party-service'
;
import
{
shouldAutoDisplayTutorial
}
from
'../helpers/session'
;
import
{
applyTheme
}
from
'../helpers/theme'
;
import
{
withServices
}
from
'../service-context'
;
...
...
@@ -62,6 +63,12 @@ function HypothesisApp({
}
},
[
isSidebar
,
profile
,
settings
,
store
]);
const
isThirdParty
=
isThirdPartyService
(
settings
);
const
exportAnnotations
=
store
.
isFeatureEnabled
(
'export_annotations'
);
const
importAnnotations
=
store
.
isFeatureEnabled
(
'import_annotations'
);
const
showShareButton
=
!
isThirdParty
||
exportAnnotations
||
importAnnotations
;
const
login
=
async
()
=>
{
if
(
serviceConfig
(
settings
))
{
// Let the host page handle the login request
...
...
@@ -155,12 +162,19 @@ function HypothesisApp({
onSignUp=
{
signUp
}
onLogout=
{
logout
}
isSidebar=
{
isSidebar
}
showShareButton=
{
showShareButton
}
/>
)
}
<
div
className=
"container"
>
<
ToastMessages
/>
<
HelpPanel
/>
<
ShareDialog
/>
{
showShareButton
&&
(
<
ShareDialog
shareTab=
{
!
isThirdParty
}
exportTab=
{
exportAnnotations
}
importTab=
{
importAnnotations
}
/>
)
}
{
route
&&
(
<
main
>
...
...
src/sidebar/components/ShareDialog/ShareDialog.tsx
View file @
cef29a96
...
...
@@ -9,23 +9,34 @@ import ShareAnnotations from './ShareAnnotations';
import
TabHeader
from
'./TabHeader'
;
import
TabPanel
from
'./TabPanel'
;
export
type
ShareDialogProps
=
{
/** If true, the share tab will be rendered. Defaults to false */
shareTab
?:
boolean
;
/** If true, the export tab will be rendered. Defaults to false */
exportTab
?:
boolean
;
/** If true, the import tab will be rendered. Defaults to false */
importTab
?:
boolean
;
};
/**
* Panel with sharing options.
* - If export feature flag is enabled, will show a tabbed interface with
* share and export tabs
* - If provided tabs include `export` or `import`, will show a tabbed interface
* - Else, shows a single "Share annotations" interface
*/
export
default
function
ShareDialog
()
{
export
default
function
ShareDialog
({
shareTab
,
exportTab
,
importTab
,
}:
ShareDialogProps
)
{
const
store
=
useSidebarStore
();
const
focusedGroup
=
store
.
focusedGroup
();
const
groupName
=
(
focusedGroup
&&
focusedGroup
.
name
)
||
'...'
;
const
panelTitle
=
`Share Annotations in
${
groupName
}
`
;
const
showExportTab
=
store
.
isFeatureEnabled
(
'export_annotations'
);
const
showImportTab
=
store
.
isFeatureEnabled
(
'import_annotations'
);
const
tabbedDialog
=
showExportTab
||
showImportTab
;
const
tabbedDialog
=
exportTab
||
importTab
;
const
[
selectedTab
,
setSelectedTab
]
=
useState
<
'share'
|
'export'
|
'import'
>
(
'share'
,
// Determine initial selected tab, based on the first tab that will be displayed
shareTab
?
'share'
:
exportTab
?
'export'
:
'import'
,
);
return
(
...
...
@@ -37,17 +48,19 @@ export default function ShareDialog() {
{
tabbedDialog
&&
(
<>
<
TabHeader
>
<
Tab
id=
"share-panel-tab"
aria
-
controls=
"share-panel"
variant=
"tab"
selected=
{
selectedTab
===
'share'
}
onClick=
{
()
=>
setSelectedTab
(
'share'
)
}
textContent=
{
'Share'
}
>
Share
</
Tab
>
{
showExportTab
&&
(
{
shareTab
&&
(
<
Tab
id=
"share-panel-tab"
aria
-
controls=
"share-panel"
variant=
"tab"
selected=
{
selectedTab
===
'share'
}
onClick=
{
()
=>
setSelectedTab
(
'share'
)
}
textContent=
"Share"
>
Share
</
Tab
>
)
}
{
exportTab
&&
(
<
Tab
id=
"export-panel-tab"
aria
-
controls=
"export-panel"
...
...
@@ -59,7 +72,7 @@ export default function ShareDialog() {
Export
</
Tab
>
)
}
{
showI
mportTab
&&
(
{
i
mportTab
&&
(
<
Tab
id=
"import-panel-tab"
aria
-
controls=
"import-panel"
...
...
@@ -85,7 +98,7 @@ export default function ShareDialog() {
id=
"export-panel"
active=
{
selectedTab
===
'export'
}
aria
-
labelledby=
"export-panel-tab"
title=
{
`Export from ${
focusedGroup?.name ?? '...'
}`
}
title=
{
`Export from ${
groupName
}`
}
>
<
ExportAnnotations
/>
</
TabPanel
>
...
...
@@ -93,14 +106,14 @@ export default function ShareDialog() {
id=
"import-panel"
active=
{
selectedTab
===
'import'
}
aria
-
labelledby=
"import-panel-tab"
title=
{
`Import into ${
focusedGroup?.name ?? '...'
}`
}
title=
{
`Import into ${
groupName
}`
}
>
<
ImportAnnotations
/>
</
TabPanel
>
</
Card
>
</>
)
}
{
!
tabbedDialog
&&
<
ShareAnnotations
/>
}
{
shareTab
&&
!
tabbedDialog
&&
<
ShareAnnotations
/>
}
</
SidebarPanel
>
);
}
src/sidebar/components/ShareDialog/test/ShareDialog-test.js
View file @
cef29a96
...
...
@@ -14,12 +14,12 @@ describe('ShareDialog', () => {
id
:
'testprivate'
,
};
const
createComponent
=
()
=>
mount
(
<
ShareDialog
/>
);
const
createComponent
=
(
props
=
{})
=>
mount
(
<
ShareDialog
shareTab
exportTab
importTab
{...
props
}
/>
)
;
beforeEach
(()
=>
{
fakeStore
=
{
focusedGroup
:
sinon
.
stub
().
returns
(
fakePrivateGroup
),
isFeatureEnabled
:
sinon
.
stub
().
returns
(
false
),
};
$imports
.
$mock
(
mockImportedComponents
());
...
...
@@ -58,10 +58,6 @@ describe('ShareDialog', () => {
});
});
function
enableFeature
(
feature
)
{
fakeStore
.
isFeatureEnabled
.
withArgs
(
feature
).
returns
(
true
);
}
function
selectTab
(
wrapper
,
name
)
{
wrapper
.
find
(
`Tab[aria-controls="
${
name
}
-panel"]`
)
...
...
@@ -77,17 +73,20 @@ describe('ShareDialog', () => {
return
wrapper
.
find
(
'TabPanel'
).
filter
({
active
:
true
});
}
it
(
'does not render a tabbed dialog if
import/export feature flags are not enabl
ed'
,
()
=>
{
const
wrapper
=
createComponent
();
it
(
'does not render a tabbed dialog if
only share tab is provid
ed'
,
()
=>
{
const
wrapper
=
createComponent
(
{
exportTab
:
false
,
importTab
:
false
}
);
assert
.
isFalse
(
wrapper
.
find
(
'TabHeader'
).
exists
());
});
[
'export_annotations'
,
'import_annotations'
].
forEach
(
feature
=>
{
it
(
`renders a tabbed dialog when
${
feature
}
feature is enabled`
,
()
=>
{
enableFeature
(
'export_annotations'
);
const
wrapper
=
createComponent
();
[
[{
shareTab
:
false
}],
[{
importTab
:
false
}],
[{
exportTab
:
false
}],
[{}],
].
forEach
(
props
=>
{
it
(
`renders a tabbed dialog when more than one tab is provided`
,
()
=>
{
const
wrapper
=
createComponent
(
props
);
assert
.
isTrue
(
wrapper
.
find
(
'TabHeader'
).
exists
());
assert
.
isTrue
(
...
...
@@ -98,9 +97,6 @@ describe('ShareDialog', () => {
});
it
(
'shows correct tab panel when each tab is clicked'
,
()
=>
{
enableFeature
(
'export_annotations'
);
enableFeature
(
'import_annotations'
);
const
wrapper
=
createComponent
();
selectTab
(
wrapper
,
'export'
);
...
...
@@ -119,24 +115,30 @@ describe('ShareDialog', () => {
assert
.
equal
(
activeTabPanel
(
wrapper
).
props
().
id
,
'share-panel'
);
});
describe
(
'a11y'
,
()
=>
{
beforeEach
(()
=>
{
enableFeature
(
'export_annotations'
);
enableFeature
(
'import_annotations'
);
it
(
'renders empty if no tabs should be displayed'
,
()
=>
{
const
wrapper
=
createComponent
({
shareTab
:
false
,
exportTab
:
false
,
importTab
:
false
,
});
assert
.
isFalse
(
wrapper
.
exists
(
'TabHeader'
));
assert
.
isFalse
(
wrapper
.
exists
(
'ShareAnnotations'
));
});
describe
(
'a11y'
,
()
=>
{
it
(
'should pass a11y checks'
,
checkAccessibility
({
content
:
()
=>
// ShareDialog renders a Fragment as its top-level component when
//
`export_annotations` feature is enabled
.
//
it has import and/or export tabs
.
// Wrapping it in a `div` ensures `checkAccessibility` internal logic
// does not discard all the Fragment children but the first one.
// See https://github.com/hypothesis/client/issues/5671
mount
(
<
div
>
<
ShareDialog
/>
<
ShareDialog
shareTab
exportTab
importTab
/>
<
/div>
,
),
}),
...
...
src/sidebar/components/TopBar.tsx
View file @
cef29a96
...
...
@@ -8,7 +8,6 @@ import classnames from 'classnames';
import
type
{
SidebarSettings
}
from
'../../types/config'
;
import
{
serviceConfig
}
from
'../config/service-config'
;
import
{
isThirdPartyService
}
from
'../helpers/is-third-party-service'
;
import
{
applyTheme
}
from
'../helpers/theme'
;
import
{
withServices
}
from
'../service-context'
;
import
type
{
FrameSyncService
}
from
'../services/frame-sync'
;
...
...
@@ -21,6 +20,8 @@ import StreamSearchInput from './StreamSearchInput';
import
UserMenu
from
'./UserMenu'
;
export
type
TopBarProps
=
{
showShareButton
:
boolean
;
/** Flag indicating whether the app is in a sidebar context */
isSidebar
:
boolean
;
...
...
@@ -49,8 +50,8 @@ function TopBar({
onSignUp
,
frameSync
,
settings
,
showShareButton
,
}:
TopBarProps
)
{
const
showSharePageButton
=
!
isThirdPartyService
(
settings
);
const
loginLinkStyle
=
applyTheme
([
'accentColor'
],
settings
);
const
store
=
useSidebarStore
();
...
...
@@ -106,7 +107,7 @@ function TopBar({
onSearch=
{
store
.
setFilterQuery
}
/>
<
SortMenu
/>
{
showShare
Page
Button
&&
(
{
showShareButton
&&
(
<
IconButton
icon=
{
ShareIcon
}
expanded=
{
isAnnotationsPanelOpen
}
...
...
src/sidebar/components/test/HypothesisApp-test.js
View file @
cef29a96
...
...
@@ -14,6 +14,7 @@ describe('HypothesisApp', () => {
let
fakeShouldAutoDisplayTutorial
=
null
;
let
fakeSettings
=
null
;
let
fakeToastMessenger
=
null
;
let
fakeIsThirdPartyService
;
const
createComponent
=
(
props
=
{})
=>
{
return
mount
(
...
...
@@ -53,6 +54,7 @@ describe('HypothesisApp', () => {
route
:
sinon
.
stub
().
returns
(
'sidebar'
),
getLink
:
sinon
.
stub
(),
isFeatureEnabled
:
sinon
.
stub
().
returns
(
true
),
};
fakeAuth
=
{};
...
...
@@ -76,6 +78,8 @@ describe('HypothesisApp', () => {
fakeConfirm
=
sinon
.
stub
().
resolves
(
false
);
fakeIsThirdPartyService
=
sinon
.
stub
().
returns
(
false
);
$imports
.
$mock
(
mockImportedComponents
());
$imports
.
$mock
({
'../config/service-config'
:
{
serviceConfig
:
fakeServiceConfig
},
...
...
@@ -85,6 +89,9 @@ describe('HypothesisApp', () => {
},
'../helpers/theme'
:
{
applyTheme
:
fakeApplyTheme
},
'../../shared/prompts'
:
{
confirm
:
fakeConfirm
},
'../helpers/is-third-party-service'
:
{
isThirdPartyService
:
fakeIsThirdPartyService
,
},
});
});
...
...
@@ -400,4 +407,21 @@ describe('HypothesisApp', () => {
assert
.
isFalse
(
container
.
hasClass
(
'theme-clean'
));
});
});
context
(
'when there are no sharing tabs to show'
,
()
=>
{
beforeEach
(()
=>
{
fakeStore
.
isFeatureEnabled
.
returns
(
false
);
fakeIsThirdPartyService
.
returns
(
true
);
});
it
(
'does not render ShareDialog'
,
()
=>
{
const
wrapper
=
createComponent
();
assert
.
isFalse
(
wrapper
.
exists
(
'ShareDialog'
));
});
it
(
'disables share button in TopBar'
,
()
=>
{
const
wrapper
=
createComponent
();
assert
.
isFalse
(
wrapper
.
find
(
'TopBar'
).
prop
(
'showShareButton'
));
});
});
});
src/sidebar/components/test/TopBar-test.js
View file @
cef29a96
...
...
@@ -9,12 +9,9 @@ describe('TopBar', () => {
let
fakeFrameSync
;
let
fakeStore
;
let
fakeStreamer
;
let
fakeIsThirdPartyService
;
let
fakeServiceConfig
;
beforeEach
(()
=>
{
fakeIsThirdPartyService
=
sinon
.
stub
().
returns
(
false
);
fakeStore
=
{
filterQuery
:
sinon
.
stub
().
returns
(
null
),
hasFetchedProfile
:
sinon
.
stub
().
returns
(
false
),
...
...
@@ -38,9 +35,6 @@ describe('TopBar', () => {
$imports
.
$mock
({
'../store'
:
{
useSidebarStore
:
()
=>
fakeStore
},
'../helpers/is-third-party-service'
:
{
isThirdPartyService
:
fakeIsThirdPartyService
,
},
'../config/service-config'
:
{
serviceConfig
:
fakeServiceConfig
},
});
});
...
...
@@ -63,6 +57,7 @@ describe('TopBar', () => {
isSidebar
=
{
true
}
settings
=
{
fakeSettings
}
streamer
=
{
fakeStreamer
}
showShareButton
{...
props
}
/>
,
);
...
...
@@ -149,13 +144,6 @@ describe('TopBar', () => {
});
});
it
(
"checks whether we're using a third-party service"
,
()
=>
{
createTopBar
();
assert
.
called
(
fakeIsThirdPartyService
);
assert
.
alwaysCalledWithExactly
(
fakeIsThirdPartyService
,
fakeSettings
);
});
context
(
'when using a first-party service'
,
()
=>
{
it
(
'shows the share annotations button'
,
()
=>
{
const
wrapper
=
createTopBar
();
...
...
@@ -163,13 +151,9 @@ describe('TopBar', () => {
});
});
context
(
'when using a third-party service'
,
()
=>
{
beforeEach
(()
=>
{
fakeIsThirdPartyService
.
returns
(
true
);
});
context
(
'when showShareButton is false'
,
()
=>
{
it
(
"doesn't show the share annotations button"
,
()
=>
{
const
wrapper
=
createTopBar
();
const
wrapper
=
createTopBar
(
{
showShareButton
:
false
}
);
assert
.
isFalse
(
wrapper
.
exists
(
'[title="Share annotations on this page"]'
),
);
...
...
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