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
ee714018
Unverified
Commit
ee714018
authored
Nov 08, 2019
by
Lyza Gardner
Committed by
GitHub
Nov 08, 2019
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #1476 from hypothesis/help-panel
Replace Help and Tutorial panels with single, preact panel
parents
518578a9
c793ad9b
Changes
25
Hide whitespace changes
Inline
Side-by-side
Showing
25 changed files
with
822 additions
and
740 deletions
+822
-740
help-link.js
src/sidebar/components/help-link.js
+0
-53
help-panel.js
src/sidebar/components/help-panel.js
+148
-31
hypothesis-app.js
src/sidebar/components/hypothesis-app.js
+20
-17
sidebar-tutorial.js
src/sidebar/components/sidebar-tutorial.js
+0
-46
help-link-test.js
src/sidebar/components/test/help-link-test.js
+0
-85
help-panel-test.js
src/sidebar/components/test/help-panel-test.js
+181
-29
hypothesis-app-test.js
src/sidebar/components/test/hypothesis-app-test.js
+36
-77
sidebar-tutorial-test.js
src/sidebar/components/test/sidebar-tutorial-test.js
+0
-82
top-bar-test.js
src/sidebar/components/test/top-bar-test.js
+49
-17
tutorial-test.js
src/sidebar/components/test/tutorial-test.js
+58
-0
version-info-test.js
src/sidebar/components/test/version-info-test.js
+36
-0
top-bar.js
src/sidebar/components/top-bar.js
+49
-23
tutorial.js
src/sidebar/components/tutorial.js
+85
-0
version-info.js
src/sidebar/components/version-info.js
+37
-0
index.js
src/sidebar/index.js
+2
-4
help-panel.html
src/sidebar/templates/help-panel.html
+0
-45
hypothesis-app.html
src/sidebar/templates/hypothesis-app.html
+1
-6
sidebar-tutorial.html
src/sidebar/templates/sidebar-tutorial.html
+0
-76
ui-constants.js
src/sidebar/ui-constants.js
+1
-0
help-panel.scss
src/styles/sidebar/components/help-panel.scss
+62
-36
sidebar-panel.scss
src/styles/sidebar/components/sidebar-panel.scss
+12
-2
sidebar-tutorial.scss
src/styles/sidebar/components/sidebar-tutorial.scss
+0
-110
tutorial.scss
src/styles/sidebar/components/tutorial.scss
+22
-0
version-info.scss
src/styles/sidebar/components/version-info.scss
+21
-0
sidebar.scss
src/styles/sidebar/sidebar.scss
+2
-1
No files found.
src/sidebar/components/help-link.js
deleted
100644 → 0
View file @
518578a9
'use strict'
;
const
propTypes
=
require
(
'prop-types'
);
const
{
createElement
}
=
require
(
'preact'
);
/**
* Render an HTML link for sending an email to Hypothes.is support. This link
* pre-populates the email body with various details about the app's state.
*/
function
HelpLink
({
auth
,
dateTime
,
documentFingerprint
=
'-'
,
url
,
userAgent
,
version
,
})
{
const
toAddress
=
'support@hypothes.is'
;
const
subject
=
encodeURIComponent
(
'Hypothesis Support'
);
const
username
=
auth
.
username
?
auth
.
username
:
'-'
;
// URL-encode informational key-value pairs for the email's body content
const
bodyAttrs
=
[
`Version:
${
version
}
`
,
`User Agent:
${
userAgent
}
`
,
`URL:
${
url
}
`
,
`PDF Fingerprint:
${
documentFingerprint
}
`
,
`Date:
${
dateTime
}
`
,
`Username:
${
username
}
`
,
].
map
(
x
=>
encodeURIComponent
(
x
));
// Create a pre-populated email body with each key-value pair on its own line
const
body
=
bodyAttrs
.
join
(
encodeURIComponent
(
'
\
r
\
n'
));
const
href
=
`mailto:
${
toAddress
}
?subject=
${
subject
}
&body=
${
body
}
`
;
return
(
<
a
className
=
"help-panel-content__link"
href
=
{
href
}
>
Send
us
an
email
<
/a
>
);
}
HelpLink
.
propTypes
=
{
auth
:
propTypes
.
object
.
isRequired
,
dateTime
:
propTypes
.
object
.
isRequired
,
documentFingerprint
:
propTypes
.
string
,
url
:
propTypes
.
string
,
userAgent
:
propTypes
.
string
.
isRequired
,
version
:
propTypes
.
string
.
isRequired
,
};
module
.
exports
=
HelpLink
;
src/sidebar/components/help-panel.js
View file @
ee714018
'use strict'
;
const
{
createElement
}
=
require
(
'preact'
);
const
{
useCallback
,
useMemo
,
useState
}
=
require
(
'preact/hooks'
);
const
propTypes
=
require
(
'prop-types'
);
const
uiConstants
=
require
(
'../ui-constants'
);
const
useStore
=
require
(
'../store/use-store'
);
const
VersionData
=
require
(
'../util/version-data'
);
const
{
withServices
}
=
require
(
'../util/service-context'
);
const
SidebarPanel
=
require
(
'./sidebar-panel'
);
const
SvgIcon
=
require
(
'./svg-icon'
);
const
Tutorial
=
require
(
'./tutorial'
);
const
VersionInfo
=
require
(
'./version-info'
);
/**
* @ngdoc directive
* @name helpPanel
* @description Displays product version and environment info
* External link "tabs" inside of the help panel.
*/
// @ngInject
module
.
exports
=
{
controllerAs
:
'vm'
,
// @ngInject
controller
:
function
(
$scope
,
$window
,
store
,
serviceUrl
)
{
this
.
userAgent
=
$window
.
navigator
.
userAgent
;
this
.
version
=
'__VERSION__'
;
// replaced by versionify
this
.
dateTime
=
new
Date
();
this
.
serviceUrl
=
serviceUrl
;
$scope
.
$watch
(
function
()
{
return
store
.
frames
();
},
function
(
frames
)
{
if
(
frames
.
length
===
0
)
{
return
;
}
this
.
url
=
frames
[
0
].
uri
;
this
.
documentFingerprint
=
frames
[
0
].
metadata
.
documentFingerprint
;
}.
bind
(
this
)
);
},
template
:
require
(
'../templates/help-panel.html'
),
bindings
:
{
auth
:
'<'
,
onClose
:
'&'
,
},
function
HelpPanelTab
({
linkText
,
url
})
{
return
(
<
div
className
=
"help-panel-tabs__tab"
>
<
a
href
=
{
url
}
className
=
"help-panel-tabs__link"
target
=
"_blank"
rel
=
"noopener noreferrer"
>
{
linkText
}{
' '
}
<
SvgIcon
name
=
"external"
className
=
"help-panel-tabs__icon"
inline
=
{
true
}
/
>
<
/a
>
<
/div
>
);
}
HelpPanelTab
.
propTypes
=
{
/* What the tab's link should say */
linkText
:
propTypes
.
string
.
isRequired
,
/* Where the tab's link should go */
url
:
propTypes
.
string
.
isRequired
,
};
/**
* A help sidebar panel with two sub-panels: tutorial and version info.
*/
function
HelpPanel
({
auth
,
session
})
{
const
mainFrame
=
useStore
(
store
=>
store
.
mainFrame
());
// Should this panel be auto-opened at app launch? Note that the actual
// auto-open triggering of this panel is owned by the `hypothesis-app` component.
// This reference is such that we know whether we should "dismiss" the tutorial
// (permanently for this user) when it is closed.
const
hasAutoDisplayPreference
=
useStore
(
store
=>
!!
store
.
getState
().
session
.
preferences
.
show_sidebar_tutorial
);
// The "Tutorial" (getting started) subpanel is the default panel shown
const
[
activeSubPanel
,
setActiveSubPanel
]
=
useState
(
'tutorial'
);
// Build version details about this session/app
const
versionData
=
useMemo
(()
=>
{
const
userInfo
=
auth
||
{};
const
documentInfo
=
mainFrame
||
{};
return
new
VersionData
(
userInfo
,
documentInfo
);
},
[
auth
,
mainFrame
]);
// The support ticket URL encodes some version info in it to pre-fill in the
// create-new-ticket form
const
supportTicketURL
=
`https://web.hypothes.is/get-help/?sys_info=
${
versionData
.
asEncodedURLString
()}
`
;
const
subPanelTitles
=
{
tutorial
:
'Getting started'
,
versionInfo
:
'About this version'
,
};
const
openSubPanel
=
(
e
,
panelName
)
=>
{
e
.
preventDefault
();
setActiveSubPanel
(
panelName
);
};
const
dismissFn
=
session
.
dismissSidebarTutorial
;
// Reference for useCallback dependency
const
onActiveChanged
=
useCallback
(
active
=>
{
if
(
!
active
&&
hasAutoDisplayPreference
)
{
// If the tutorial is currently being auto-displayed, update the user
// preference to disable the auto-display from happening on subsequent
// app launches
dismissFn
();
}
},
[
dismissFn
,
hasAutoDisplayPreference
]
);
return
(
<
SidebarPanel
title
=
"Need some help?"
panelName
=
{
uiConstants
.
PANEL_HELP
}
onActiveChanged
=
{
onActiveChanged
}
>
<
h3
className
=
"help-panel__sub-panel-title"
>
{
subPanelTitles
[
activeSubPanel
]}
<
/h3
>
<
div
className
=
"help-panel__content"
>
{
activeSubPanel
===
'tutorial'
&&
<
Tutorial
/>
}
{
activeSubPanel
===
'versionInfo'
&&
(
<
VersionInfo
versionData
=
{
versionData
}
/
>
)}
<
div
className
=
"help-panel__footer"
>
{
activeSubPanel
===
'versionInfo'
&&
(
<
a
href
=
"#"
className
=
"help-panel__sub-panel-link help-panel__sub-panel-link--left"
onClick
=
{
e
=>
openSubPanel
(
e
,
'tutorial'
)}
>
<
SvgIcon
name
=
"arrow-left"
className
=
"help-panel__icon"
/>
<
div
>
Getting
started
<
/div
>
<
/a
>
)}
{
activeSubPanel
===
'tutorial'
&&
(
<
a
href
=
"#"
className
=
"help-panel__sub-panel-link help-panel__sub-panel-link--right"
onClick
=
{
e
=>
openSubPanel
(
e
,
'versionInfo'
)}
>
<
div
>
About
this
version
<
/div
>
<
SvgIcon
name
=
"arrow-right"
className
=
"help-panel__icon"
/>
<
/a
>
)}
<
/div
>
<
/div
>
<
div
className
=
"help-panel-tabs"
>
<
HelpPanelTab
linkText
=
"Help topics"
url
=
"https://web.hypothes.is/help/"
/>
<
HelpPanelTab
linkText
=
"New support ticket"
url
=
{
supportTicketURL
}
/
>
<
/div
>
<
/SidebarPanel
>
);
}
HelpPanel
.
propTypes
=
{
/* Object with auth and user information */
auth
:
propTypes
.
object
.
isRequired
,
session
:
propTypes
.
object
.
isRequired
,
};
HelpPanel
.
injectedProps
=
[
'session'
];
module
.
exports
=
withServices
(
HelpPanel
);
src/sidebar/components/hypothesis-app.js
View file @
ee714018
...
...
@@ -4,6 +4,9 @@ const events = require('../events');
const
{
parseAccountID
}
=
require
(
'../util/account-id'
);
const
serviceConfig
=
require
(
'../service-config'
);
const
bridgeEvents
=
require
(
'../../shared/bridge-events'
);
const
uiConstants
=
require
(
'../ui-constants'
);
const
isSidebar
=
require
(
'../util/is-sidebar'
);
const
{
shouldAutoDisplayTutorial
}
=
require
(
'../util/session-util'
);
/**
* Return the user's authentication status from their profile.
...
...
@@ -56,23 +59,34 @@ function HypothesisAppController(
// used by templates to show an intermediate or loading state.
this
.
auth
=
{
status
:
'unknown'
};
// App dialogs
this
.
helpPanel
=
{
visible
:
false
};
// Check to see if we're in the sidebar, or on a standalone page such as
// the stream page or an individual annotation page.
this
.
isSidebar
=
$window
.
top
!==
$window
;
this
.
isSidebar
=
isSidebar
()
;
if
(
this
.
isSidebar
)
{
frameSync
.
connect
();
}
// Reload the view when the user switches accounts
this
.
onUserChange
=
profile
=>
{
self
.
auth
=
authStateFromProfile
(
profile
);
if
(
shouldAutoDisplayTutorial
(
this
.
isSidebar
,
store
.
getState
().
session
,
settings
)
)
{
// Auto-open the tutorial (help) panel
store
.
openSidebarPanel
(
uiConstants
.
PANEL_HELP
);
}
};
$scope
.
$on
(
events
.
USER_CHANGED
,
function
(
event
,
data
)
{
self
.
auth
=
authStateFromProfil
e
(
data
.
profile
);
self
.
onUserChang
e
(
data
.
profile
);
});
session
.
load
().
then
(
profile
=>
{
self
.
auth
=
authStateFromProfil
e
(
profile
);
self
.
onUserChang
e
(
profile
);
});
/**
...
...
@@ -110,17 +124,6 @@ function HypothesisAppController(
$window
.
open
(
serviceUrl
(
'signup'
));
};
this
.
showHelpPanel
=
function
()
{
const
service
=
serviceConfig
(
settings
)
||
{};
if
(
service
.
onHelpRequestProvided
)
{
// Let the host page handle the help request.
bridge
.
call
(
bridgeEvents
.
HELP_REQUESTED
);
return
;
}
this
.
helpPanel
.
visible
=
true
;
};
// Prompt to discard any unsaved drafts.
const
promptToLogout
=
function
()
{
// TODO - Replace this with a UI which doesn't look terrible.
...
...
src/sidebar/components/sidebar-tutorial.js
deleted
100644 → 0
View file @
518578a9
'use strict'
;
const
sessionUtil
=
require
(
'../util/session-util'
);
const
isThirdPartyService
=
require
(
'../util/is-third-party-service'
);
// @ngInject
function
SidebarTutorialController
(
session
,
settings
)
{
// Compute once since this doesn't change after the app starts.
const
isThirdPartyService_
=
isThirdPartyService
(
settings
);
this
.
isThemeClean
=
settings
.
theme
===
'clean'
;
this
.
showSidebarTutorial
=
function
()
{
return
sessionUtil
.
shouldShowSidebarTutorial
(
session
.
state
);
};
this
.
dismiss
=
function
()
{
session
.
dismissSidebarTutorial
();
};
this
.
canCreatePrivateGroup
=
()
=>
{
// Private group creation in the client is limited to first party users.
// In future we may extend this to third party users, but still disable
// private group creation in certain contexts (eg. the LMS app).
return
!
isThirdPartyService_
;
};
this
.
canSharePage
=
()
=>
{
// The "Share document" icon in the toolbar is limited to first party users.
// In future we may extend this to third party users, but still disable it
// in certain contexts (eg. the LMS app).
return
!
isThirdPartyService_
;
};
}
/**
* @name sidebarTutorial
* @description Displays a short tutorial in the sidebar.
*/
// @ngInject
module
.
exports
=
{
controller
:
SidebarTutorialController
,
controllerAs
:
'vm'
,
bindings
:
{},
template
:
require
(
'../templates/sidebar-tutorial.html'
),
};
src/sidebar/components/test/help-link-test.js
deleted
100644 → 0
View file @
518578a9
'use strict'
;
const
{
createElement
}
=
require
(
'preact'
);
const
{
mount
}
=
require
(
'enzyme'
);
const
HelpLink
=
require
(
'../help-link'
);
const
mockImportedComponents
=
require
(
'./mock-imported-components'
);
describe
(
'Help (mailto) Link'
,
()
=>
{
let
fakeAuth
;
let
fakeDateTime
;
let
fakeDocumentFingerprint
;
let
fakeUrl
;
let
fakeUserAgent
;
let
fakeVersion
;
const
createHelpLink
=
()
=>
{
return
mount
(
<
HelpLink
auth
=
{
fakeAuth
}
dateTime
=
{
fakeDateTime
}
documentFingerprint
=
{
fakeDocumentFingerprint
}
url
=
{
fakeUrl
}
userAgent
=
{
fakeUserAgent
}
version
=
{
fakeVersion
}
/
>
);
};
beforeEach
(()
=>
{
fakeAuth
=
{
username
:
'fiona.user'
,
};
fakeDateTime
=
new
Date
();
fakeDocumentFingerprint
=
'fingerprint'
;
fakeUrl
=
'http://www.example.com'
;
fakeUserAgent
=
'Some User Agent'
;
fakeVersion
=
'1.0.0'
;
HelpLink
.
$imports
.
$mock
(
mockImportedComponents
());
});
afterEach
(()
=>
{
HelpLink
.
$imports
.
$restore
();
});
it
(
'sets required props as part of formatted email body'
,
()
=>
{
const
wrapper
=
createHelpLink
();
const
href
=
wrapper
.
find
(
'a'
).
prop
(
'href'
);
[
{
label
:
'Version'
,
value
:
fakeVersion
},
{
label
:
'User Agent'
,
value
:
fakeUserAgent
},
{
label
:
'URL'
,
value
:
fakeUrl
},
{
label
:
'PDF Fingerprint'
,
value
:
fakeDocumentFingerprint
},
{
label
:
'Date'
,
value
:
fakeDateTime
},
{
label
:
'Username'
,
value
:
fakeAuth
.
username
},
].
forEach
(
helpInfo
=>
{
const
emailBodyPart
=
encodeURIComponent
(
`
${
helpInfo
.
label
}
:
${
helpInfo
.
value
}
`
);
assert
.
include
(
href
,
emailBodyPart
);
});
});
it
(
'sets a default value for PDF document fingerprint if not provided'
,
()
=>
{
fakeDocumentFingerprint
=
undefined
;
const
wrapper
=
createHelpLink
();
const
href
=
wrapper
.
find
(
'a'
).
prop
(
'href'
);
const
emailBodyPart
=
encodeURIComponent
(
'PDF Fingerprint: -'
);
assert
.
include
(
href
,
emailBodyPart
);
});
it
(
'sets a default value for username if no authenticated user'
,
()
=>
{
fakeAuth
=
{};
const
wrapper
=
createHelpLink
();
const
href
=
wrapper
.
find
(
'a'
).
prop
(
'href'
);
const
emailBodyPart
=
encodeURIComponent
(
'Username: -'
);
assert
.
include
(
href
,
emailBodyPart
);
});
});
src/sidebar/components/test/help-panel-test.js
View file @
ee714018
'use strict'
;
const
angular
=
require
(
'angular'
);
const
{
mount
}
=
require
(
'enzyme'
);
const
{
createElement
}
=
require
(
'preact'
);
const
{
act
}
=
require
(
'preact/test-utils'
);
describe
(
'helpPanel'
,
function
()
{
const
HelpPanel
=
require
(
'../help-panel'
);
const
mockImportedComponents
=
require
(
'./mock-imported-components'
);
describe
(
'HelpPanel'
,
function
()
{
let
fakeAuth
;
let
fakeSessionService
;
let
fakeStore
;
let
$componentController
;
let
$rootScope
;
let
fakeVersionData
;
let
fakeVersionDataObject
;
function
createComponent
(
props
)
{
return
mount
(
<
HelpPanel
auth
=
{
fakeAuth
}
session
=
{
fakeSessionService
}
{...
props
}
/
>
);
}
beforeEach
(
function
()
{
beforeEach
(()
=>
{
fakeAuth
=
{};
fakeSessionService
=
{
dismissSidebarTutorial
:
sinon
.
stub
()
};
fakeStore
=
{
frames
:
sinon
.
stub
().
returns
([]),
getState
:
sinon
.
stub
()
.
returns
({
session
:
{
preferences
:
{
show_sidebar_tutorial
:
true
}
}
}),
mainFrame
:
sinon
.
stub
().
returns
(
null
),
};
fakeVersionDataObject
=
{
asEncodedURLString
:
sinon
.
stub
().
returns
(
'fakeURLString'
),
};
fakeVersionData
=
sinon
.
stub
().
returns
(
fakeVersionDataObject
);
HelpPanel
.
$imports
.
$mock
(
mockImportedComponents
());
HelpPanel
.
$imports
.
$mock
({
'../store/use-store'
:
callback
=>
callback
(
fakeStore
),
'../util/version-data'
:
fakeVersionData
,
});
});
afterEach
(()
=>
{
HelpPanel
.
$imports
.
$restore
();
});
context
(
'when viewing tutorial sub-panel'
,
()
=>
{
it
(
'should show tutorial by default'
,
()
=>
{
const
wrapper
=
createComponent
();
const
subHeader
=
wrapper
.
find
(
'.help-panel__sub-panel-title'
);
assert
.
equal
(
subHeader
.
text
(),
'Getting started'
);
assert
.
isTrue
(
wrapper
.
find
(
'Tutorial'
).
exists
());
assert
.
isFalse
(
wrapper
.
find
(
'VersionInfo'
).
exists
());
});
it
(
'should show navigation link to versionInfo sub-panel'
,
()
=>
{
const
wrapper
=
createComponent
();
const
link
=
wrapper
.
find
(
'.help-panel__sub-panel-link'
);
assert
.
equal
(
link
.
text
(),
'About this version'
);
});
it
(
'should switch to versionInfo sub-panel when footer link clicked'
,
()
=>
{
const
wrapper
=
createComponent
();
wrapper
.
find
(
'.help-panel__sub-panel-link'
).
simulate
(
'click'
);
assert
.
equal
(
wrapper
.
find
(
'.help-panel__sub-panel-title'
).
text
(),
'About this version'
);
assert
.
isTrue
(
wrapper
.
find
(
'VersionInfo'
).
exists
());
assert
.
equal
(
wrapper
.
find
(
'VersionInfo'
).
prop
(
'versionData'
),
fakeVersionDataObject
);
assert
.
isFalse
(
wrapper
.
find
(
'Tutorial'
).
exists
());
});
});
context
(
'when viewing versionInfo sub-panel'
,
()
=>
{
it
(
'should show navigation link back to tutorial sub-panel'
,
()
=>
{
const
wrapper
=
createComponent
();
wrapper
.
find
(
'.help-panel__sub-panel-link'
).
simulate
(
'click'
);
const
link
=
wrapper
.
find
(
'.help-panel__sub-panel-link'
);
angular
.
module
(
'h'
,
[]).
component
(
'helpPanel'
,
require
(
'../help-panel'
));
assert
.
isTrue
(
wrapper
.
find
(
'VersionInfo'
).
exists
());
assert
.
isFalse
(
wrapper
.
find
(
'Tutorial'
).
exists
());
assert
.
equal
(
link
.
text
(),
'Getting started'
);
});
it
(
'should switch to tutorial sub-panel when link clicked'
,
()
=>
{
const
wrapper
=
createComponent
();
// Click to get to version-info sub-panel...
wrapper
.
find
(
'.help-panel__sub-panel-link'
).
simulate
(
'click'
);
const
link
=
wrapper
.
find
(
'.help-panel__sub-panel-link'
);
// Click again to get back to tutorial sub-panel
link
.
simulate
(
'click'
);
assert
.
isFalse
(
wrapper
.
find
(
'VersionInfo'
).
exists
());
assert
.
isTrue
(
wrapper
.
find
(
'Tutorial'
).
exists
());
});
});
describe
(
'`HelpPanelTab`s'
,
()
=>
{
it
(
'should render static link to knowledge base'
,
()
=>
{
const
wrapper
=
createComponent
();
angular
.
mock
.
module
(
'h'
,
{
store
:
fakeStore
,
serviceUrl
:
sinon
.
stub
(),
assert
.
isTrue
(
wrapper
.
find
(
'HelpPanelTab'
)
.
filter
({
linkText
:
'Help topics'
})
.
exists
()
);
});
angular
.
mock
.
inject
(
function
(
_$componentController_
,
_$rootScope_
)
{
$componentController
=
_$componentController_
;
$rootScope
=
_$rootScope_
;
it
(
'should render dynamic link to create a new help ticket'
,
()
=>
{
const
wrapper
=
createComponent
();
const
helpTab
=
wrapper
.
find
(
'HelpPanelTab'
)
.
filter
({
linkText
:
'New support ticket'
});
assert
.
isTrue
(
helpTab
.
exists
());
assert
.
include
(
helpTab
.
prop
(
'url'
),
'fakeURLString'
);
});
});
it
(
'displays the URL and fingerprint of the first connected frame'
,
function
()
{
fakeStore
.
frames
.
returns
([
{
uri
:
'https://publisher.org/article.pdf'
,
metadata
:
{
documentFingerprint
:
'12345'
,
},
},
]);
const
$scope
=
$rootScope
.
$new
();
const
ctrl
=
$componentController
(
'helpPanel'
,
{
$scope
:
$scope
});
$scope
.
$digest
();
assert
.
equal
(
ctrl
.
url
,
'https://publisher.org/article.pdf'
);
assert
.
equal
(
ctrl
.
documentFingerprint
,
'12345'
);
context
(
'dismissing the tutorial and clearing profile setting'
,
()
=>
{
context
(
'profile preference to auto-show tutorial is truthy'
,
()
=>
{
beforeEach
(()
=>
{
fakeStore
.
getState
.
returns
({
session
:
{
preferences
:
{
show_sidebar_tutorial
:
true
}
},
});
});
it
(
'should not dismiss the panel when it is initially opened'
,
()
=>
{
const
wrapper
=
createComponent
();
const
onActiveChanged
=
wrapper
.
find
(
'SidebarPanel'
)
.
prop
(
'onActiveChanged'
);
act
(()
=>
{
// "Activate" the panel (simulate the `SidebarPanel` communicating
// an active state via callback prop)
onActiveChanged
(
true
);
});
assert
.
notOk
(
fakeSessionService
.
dismissSidebarTutorial
.
callCount
);
});
it
(
'should invoke dismiss service method when panel is first closed'
,
()
=>
{
const
wrapper
=
createComponent
();
const
onActiveChanged
=
wrapper
.
find
(
'SidebarPanel'
)
.
prop
(
'onActiveChanged'
);
act
(()
=>
{
// "Activate" the panel (simulate the `SidebarPanel` communicating
// an active state via callback prop)
onActiveChanged
(
true
);
// Now "close" the panel
onActiveChanged
(
false
);
});
assert
.
calledOnce
(
fakeSessionService
.
dismissSidebarTutorial
);
});
});
context
(
'profile preference to auto-show tutorial is falsy'
,
()
=>
{
beforeEach
(()
=>
{
fakeStore
.
getState
.
returns
({
session
:
{
preferences
:
{
show_sidebar_tutorial
:
false
}
},
});
});
it
(
'should not invoke dismiss service method when panel is closed'
,
()
=>
{
const
wrapper
=
createComponent
();
const
onActiveChanged
=
wrapper
.
find
(
'SidebarPanel'
)
.
prop
(
'onActiveChanged'
);
act
(()
=>
{
// "Activate" the panel (simulate the `SidebarPanel` communicating
// an active state via callback prop)
onActiveChanged
(
true
);
// Now "close" the panel
onActiveChanged
(
false
);
});
assert
.
notOk
(
fakeSessionService
.
dismissSidebarTutorial
.
callCount
);
});
});
});
});
src/sidebar/components/test/hypothesis-app-test.js
View file @
ee714018
...
...
@@ -18,9 +18,11 @@ describe('sidebar.components.hypothesis-app', function() {
let
fakeFeatures
=
null
;
let
fakeFlash
=
null
;
let
fakeFrameSync
=
null
;
let
fakeIsSidebar
=
null
;
let
fakeParams
=
null
;
let
fakeServiceConfig
=
null
;
let
fakeSession
=
null
;
let
fakeShouldAutoDisplayTutorial
=
null
;
let
fakeGroups
=
null
;
let
fakeRoute
=
null
;
let
fakeServiceUrl
=
null
;
...
...
@@ -40,10 +42,16 @@ describe('sidebar.components.hypothesis-app', function() {
});
beforeEach
(
function
()
{
fakeIsSidebar
=
sandbox
.
stub
().
returns
(
true
);
fakeServiceConfig
=
sandbox
.
stub
();
fakeShouldAutoDisplayTutorial
=
sinon
.
stub
().
returns
(
false
);
hypothesisApp
.
$imports
.
$mock
({
'../util/is-sidebar'
:
fakeIsSidebar
,
'../service-config'
:
fakeServiceConfig
,
'../util/session-util'
:
{
shouldAutoDisplayTutorial
:
fakeShouldAutoDisplayTutorial
,
},
});
angular
.
module
(
'h'
,
[]).
component
(
'hypothesisApp'
,
hypothesisApp
);
...
...
@@ -60,8 +68,15 @@ describe('sidebar.components.hypothesis-app', function() {
fakeStore
=
{
tool
:
'comment'
,
clearSelectedAnnotations
:
sandbox
.
spy
(),
getState
:
sinon
.
stub
(),
getState
:
sinon
.
stub
().
returns
({
session
:
{
preferences
:
{
show_sidebar_tutorial
:
false
,
},
},
}),
clearGroups
:
sinon
.
stub
(),
openSidebarPanel
:
sinon
.
stub
(),
// draft store
countDrafts
:
sandbox
.
stub
().
returns
(
0
),
discardAllDrafts
:
sandbox
.
stub
(),
...
...
@@ -143,32 +158,36 @@ describe('sidebar.components.hypothesis-app', function() {
sandbox
.
restore
();
});
describe
(
'#isSidebar'
,
function
()
{
it
(
'is false if the window is the top window'
,
function
()
{
fakeWindow
.
top
=
fakeWindow
;
const
ctrl
=
createController
();
assert
.
isFalse
(
ctrl
.
isSidebar
);
});
it
(
'is true if the window is not the top window'
,
function
()
{
fakeWindow
.
top
=
{};
const
ctrl
=
createController
();
assert
.
isTrue
(
ctrl
.
isSidebar
);
});
});
it
(
'connects to host frame in the sidebar app'
,
function
()
{
fake
Window
.
top
=
{}
;
fake
IsSidebar
.
returns
(
true
)
;
createController
();
assert
.
called
(
fakeFrameSync
.
connect
);
});
it
(
'does not connect to the host frame in the stream'
,
function
()
{
fake
Window
.
top
=
fakeWindow
;
fake
IsSidebar
.
returns
(
false
)
;
createController
();
assert
.
notCalled
(
fakeFrameSync
.
connect
);
});
describe
(
'auto-opening tutorial'
,
()
=>
{
it
(
'should open tutorial on profile load when criteria are met'
,
()
=>
{
fakeShouldAutoDisplayTutorial
.
returns
(
true
);
createController
();
return
fakeSession
.
load
().
then
(()
=>
{
assert
.
calledOnce
(
fakeStore
.
openSidebarPanel
);
});
});
it
(
'should not open tutorial on profile load when criteria are not met'
,
()
=>
{
fakeShouldAutoDisplayTutorial
.
returns
(
false
);
createController
();
return
fakeSession
.
load
().
then
(()
=>
{
assert
.
equal
(
fakeStore
.
openSidebarPanel
.
callCount
,
0
);
});
});
});
it
(
'auth.status is "unknown" on startup'
,
function
()
{
const
ctrl
=
createController
();
assert
.
equal
(
ctrl
.
auth
.
status
,
'unknown'
);
...
...
@@ -290,66 +309,6 @@ describe('sidebar.components.hypothesis-app', function() {
});
});
describe
(
'#showHelpPanel'
,
function
()
{
context
(
'when using a third-party service'
,
function
()
{
context
(
"when there's no onHelpRequest callback function"
,
function
()
{
beforeEach
(
'configure a service with no onHelpRequest'
,
function
()
{
fakeServiceConfig
.
returns
({});
});
it
(
'does not send an event'
,
function
()
{
createController
().
showHelpPanel
();
assert
.
notCalled
(
fakeBridge
.
call
);
});
it
(
'shows the help panel'
,
function
()
{
const
ctrl
=
createController
();
ctrl
.
showHelpPanel
();
assert
.
isTrue
(
ctrl
.
helpPanel
.
visible
);
});
});
context
(
"when there's an onHelpRequest callback function"
,
function
()
{
beforeEach
(
'provide an onHelpRequest callback'
,
function
()
{
fakeServiceConfig
.
returns
({
onHelpRequestProvided
:
true
});
});
it
(
'sends the HELP_REQUESTED event'
,
function
()
{
createController
().
showHelpPanel
();
assert
.
calledWith
(
fakeBridge
.
call
,
bridgeEvents
.
HELP_REQUESTED
);
});
it
(
'does not show the help panel'
,
function
()
{
const
ctrl
=
createController
();
ctrl
.
showHelpPanel
();
assert
.
isFalse
(
ctrl
.
helpPanel
.
visible
);
});
});
});
context
(
'when not using a third-party service'
,
function
()
{
it
(
'does not send an event'
,
function
()
{
createController
().
showHelpPanel
();
assert
.
notCalled
(
fakeBridge
.
call
);
});
it
(
'shows the help panel'
,
function
()
{
const
ctrl
=
createController
();
ctrl
.
showHelpPanel
();
assert
.
isTrue
(
ctrl
.
helpPanel
.
visible
);
});
});
});
describe
(
'#login()'
,
function
()
{
beforeEach
(()
=>
{
fakeAuth
.
login
=
sinon
.
stub
().
returns
(
Promise
.
resolve
());
...
...
src/sidebar/components/test/sidebar-tutorial-test.js
deleted
100644 → 0
View file @
518578a9
'use strict'
;
const
Controller
=
require
(
'../sidebar-tutorial'
).
controller
;
describe
(
'sidebar/components/sidebar-tutorial'
,
function
()
{
const
defaultSession
=
{
state
:
{
preferences
:
{}
}
};
const
firstPartySettings
=
{};
const
thirdPartySettings
=
{
services
:
[
{
authority
:
'publisher.org'
,
},
],
};
describe
(
'#showSidebarTutorial'
,
function
()
{
const
settings
=
{};
it
(
'returns true if show_sidebar_tutorial is true'
,
function
()
{
const
session
=
{
state
:
{
preferences
:
{
show_sidebar_tutorial
:
true
,
},
},
};
const
controller
=
new
Controller
(
session
,
settings
);
const
result
=
controller
.
showSidebarTutorial
();
assert
.
equal
(
result
,
true
);
});
it
(
'returns false if show_sidebar_tutorial is false'
,
function
()
{
const
session
=
{
state
:
{
preferences
:
{
show_sidebar_tutorial
:
false
,
},
},
};
const
controller
=
new
Controller
(
session
,
settings
);
const
result
=
controller
.
showSidebarTutorial
();
assert
.
equal
(
result
,
false
);
});
it
(
'returns false if show_sidebar_tutorial is missing'
,
function
()
{
const
session
=
{
state
:
{
preferences
:
{}
}
};
const
controller
=
new
Controller
(
session
,
settings
);
const
result
=
controller
.
showSidebarTutorial
();
assert
.
equal
(
result
,
false
);
});
});
describe
(
'#canSharePage'
,
()
=>
{
it
(
'is true for first party users'
,
()
=>
{
const
controller
=
new
Controller
(
defaultSession
,
firstPartySettings
);
assert
.
isTrue
(
controller
.
canSharePage
());
});
it
(
'is false for third party users'
,
()
=>
{
const
controller
=
new
Controller
(
defaultSession
,
thirdPartySettings
);
assert
.
isFalse
(
controller
.
canSharePage
());
});
});
describe
(
'#canCreatePrivateGroup'
,
()
=>
{
it
(
'is true for first party users'
,
()
=>
{
const
controller
=
new
Controller
(
defaultSession
,
firstPartySettings
);
assert
.
isTrue
(
controller
.
canSharePage
());
});
it
(
'is false for third party users'
,
()
=>
{
const
controller
=
new
Controller
(
defaultSession
,
thirdPartySettings
);
assert
.
isFalse
(
controller
.
canSharePage
());
});
});
});
src/sidebar/components/test/top-bar-test.js
View file @
ee714018
...
...
@@ -4,15 +4,18 @@ const { createElement } = require('preact');
const
{
mount
}
=
require
(
'enzyme'
);
const
uiConstants
=
require
(
'../../ui-constants'
);
const
bridgeEvents
=
require
(
'../../../shared/bridge-events'
);
const
TopBar
=
require
(
'../top-bar'
);
const
mockImportedComponents
=
require
(
'./mock-imported-components'
);
describe
(
'TopBar'
,
()
=>
{
const
fakeSettings
=
{};
let
fakeBridge
;
let
fakeStore
;
let
fakeStreamer
;
let
fakeIsThirdPartyService
;
let
fakeServiceConfig
;
beforeEach
(()
=>
{
fakeIsThirdPartyService
=
sinon
.
stub
().
returns
(
false
);
...
...
@@ -29,6 +32,12 @@ describe('TopBar', () => {
toggleSidebarPanel
:
sinon
.
stub
(),
};
fakeBridge
=
{
call
:
sinon
.
stub
(),
};
fakeServiceConfig
=
sinon
.
stub
().
returns
({});
fakeStreamer
=
{
applyPendingUpdates
:
sinon
.
stub
(),
};
...
...
@@ -37,6 +46,7 @@ describe('TopBar', () => {
TopBar
.
$imports
.
$mock
({
'../store/use-store'
:
callback
=>
callback
(
fakeStore
),
'../util/is-third-party-service'
:
fakeIsThirdPartyService
,
'../service-config'
:
fakeServiceConfig
,
});
});
...
...
@@ -57,6 +67,7 @@ describe('TopBar', () => {
return
mount
(
<
TopBar
auth
=
{
auth
}
bridge
=
{
fakeBridge
}
isSidebar
=
{
true
}
settings
=
{
fakeSettings
}
streamer
=
{
fakeStreamer
}
...
...
@@ -86,14 +97,41 @@ describe('TopBar', () => {
assert
.
called
(
fakeStreamer
.
applyPendingUpdates
);
});
it
(
'shows Help Panel when help icon is clicked'
,
()
=>
{
const
onShowHelpPanel
=
sinon
.
stub
();
const
wrapper
=
createTopBar
({
onShowHelpPanel
:
onShowHelpPanel
,
describe
(
'`HelpButton` and help requests'
,
()
=>
{
context
(
'no help service handler configured in services (default)'
,
()
=>
{
it
(
'toggles Help Panel on click'
,
()
=>
{
const
wrapper
=
createTopBar
();
const
help
=
helpBtn
(
wrapper
);
help
.
simulate
(
'click'
);
assert
.
calledWith
(
fakeStore
.
toggleSidebarPanel
,
uiConstants
.
PANEL_HELP
);
});
it
(
'displays a help icon active state when help panel active'
,
()
=>
{
// state returning active sidebar panel as `PANEL_HELP` triggers active class
fakeStore
.
getState
=
sinon
.
stub
().
returns
({
sidebarPanels
:
{
activePanelName
:
uiConstants
.
PANEL_HELP
,
},
});
const
wrapper
=
createTopBar
();
const
help
=
helpBtn
(
wrapper
);
wrapper
.
update
();
assert
.
isTrue
(
help
.
hasClass
(
'top-bar__btn--active'
));
assert
.
isOk
(
help
.
prop
(
'aria-expanded'
));
});
context
(
'help service handler configured in services'
,
()
=>
{
it
(
'fires a bridge event if help clicked and service is configured'
,
()
=>
{
fakeServiceConfig
.
returns
({
onHelpRequestProvided
:
true
});
const
wrapper
=
createTopBar
();
const
help
=
helpBtn
(
wrapper
);
help
.
simulate
(
'click'
);
assert
.
equal
(
fakeStore
.
toggleSidebarPanel
.
callCount
,
0
);
assert
.
calledWith
(
fakeBridge
.
call
,
bridgeEvents
.
HELP_REQUESTED
);
});
});
});
const
help
=
helpBtn
(
wrapper
);
help
.
simulate
(
'click'
);
assert
.
called
(
onShowHelpPanel
);
});
describe
(
'login/account actions'
,
()
=>
{
...
...
@@ -170,7 +208,10 @@ describe('TopBar', () => {
it
(
'toggles the share annotations panel when "Share" is clicked'
,
()
=>
{
const
wrapper
=
createTopBar
();
wrapper
.
find
(
'[title="Share annotations on this page"]'
).
simulate
(
'click'
);
assert
.
called
(
fakeStore
.
toggleSidebarPanel
);
assert
.
calledWith
(
fakeStore
.
toggleSidebarPanel
,
uiConstants
.
PANEL_SHARE_ANNOTATIONS
);
});
it
(
'adds an active-state class to the "Share" icon when the panel is open'
,
()
=>
{
...
...
@@ -217,14 +258,5 @@ describe('TopBar', () => {
assert
.
isFalse
(
wrapper
.
exists
(
'SortMenu'
));
assert
.
isFalse
(
wrapper
.
exists
(
'button[title="Share this page"]'
));
});
it
(
'does show the Help menu and user menu'
,
()
=>
{
const
wrapper
=
createTopBar
({
isSidebar
:
false
,
auth
:
{
status
:
'logged-in'
},
});
assert
.
isTrue
(
wrapper
.
exists
(
'button[title="Help"]'
));
assert
.
isTrue
(
wrapper
.
exists
(
'UserMenu'
));
});
});
});
src/sidebar/components/test/tutorial-test.js
0 → 100644
View file @
ee714018
'use strict'
;
const
{
mount
}
=
require
(
'enzyme'
);
const
{
createElement
}
=
require
(
'preact'
);
const
Tutorial
=
require
(
'../tutorial'
);
const
mockImportedComponents
=
require
(
'./mock-imported-components'
);
describe
(
'Tutorial'
,
function
()
{
let
fakeIsThirdPartyService
;
function
createComponent
(
props
)
{
return
mount
(
<
Tutorial
settings
=
{{}}
{...
props
}
/>
)
;
}
beforeEach
(()
=>
{
fakeIsThirdPartyService
=
sinon
.
stub
().
returns
(
false
);
Tutorial
.
$imports
.
$mock
(
mockImportedComponents
());
Tutorial
.
$imports
.
$mock
({
'../util/is-third-party-service'
:
fakeIsThirdPartyService
,
});
});
afterEach
(()
=>
{
Tutorial
.
$imports
.
$restore
();
});
it
(
'should show four "steps" of instructions to first-party users'
,
()
=>
{
const
wrapper
=
createComponent
();
const
tutorialEntries
=
wrapper
.
find
(
'li'
);
assert
.
equal
(
tutorialEntries
.
length
,
4
);
});
it
(
'should show three "steps" of instructions to third-party users'
,
()
=>
{
fakeIsThirdPartyService
.
returns
(
true
);
const
wrapper
=
createComponent
();
const
tutorialEntries
=
wrapper
.
find
(
'li'
);
assert
.
equal
(
tutorialEntries
.
length
,
3
);
});
[
{
iconName
:
'annotate'
,
commandName
:
'Annotate'
},
{
iconName
:
'highlight'
,
commandName
:
'Highlight'
},
{
iconName
:
'reply'
,
commandName
:
'Reply'
},
].
forEach
(
testCase
=>
{
it
(
`renders expected
${
testCase
.
commandName
}
TutorialInstruction`
,
()
=>
{
const
wrapper
=
createComponent
();
const
instruction
=
wrapper
.
find
(
'TutorialInstruction'
).
filter
({
iconName
:
testCase
.
iconName
,
commandName
:
testCase
.
commandName
,
});
assert
.
isTrue
(
instruction
.
exists
());
});
});
});
src/sidebar/components/test/version-info-test.js
0 → 100644
View file @
ee714018
'use strict'
;
const
{
mount
}
=
require
(
'enzyme'
);
const
{
createElement
}
=
require
(
'preact'
);
const
VersionInfo
=
require
(
'../version-info'
);
describe
(
'VersionInfo'
,
function
()
{
let
fakeVersionData
;
function
createComponent
(
props
)
{
return
mount
(
<
VersionInfo
versionData
=
{
fakeVersionData
}
{...
props
}
/>
)
;
}
beforeEach
(()
=>
{
fakeVersionData
=
{
version
:
'fakeVersion'
,
userAgent
:
'fakeUserAgent'
,
url
:
'fakeUrl'
,
fingerprint
:
'fakeFingerprint'
,
account
:
'fakeAccount'
,
timestamp
:
'fakeTimestamp'
,
};
});
it
(
'renders `versionData` information'
,
()
=>
{
const
wrapper
=
createComponent
();
const
componentText
=
wrapper
.
text
();
assert
.
include
(
componentText
,
'fakeVersion'
);
assert
.
include
(
componentText
,
'fakeUserAgent'
);
assert
.
include
(
componentText
,
'fakeUrl'
);
assert
.
include
(
componentText
,
'fakeFingerprint'
);
assert
.
include
(
componentText
,
'fakeAccount'
);
assert
.
include
(
componentText
,
'fakeTimestamp'
);
});
});
src/sidebar/components/top-bar.js
View file @
ee714018
...
...
@@ -4,9 +4,11 @@ const { Fragment, createElement } = require('preact');
const
classnames
=
require
(
'classnames'
);
const
propTypes
=
require
(
'prop-types'
);
const
bridgeEvents
=
require
(
'../../shared/bridge-events'
);
const
useStore
=
require
(
'../store/use-store'
);
const
{
applyTheme
}
=
require
(
'../util/theme'
);
const
isThirdPartyService
=
require
(
'../util/is-third-party-service'
);
const
serviceConfig
=
require
(
'../service-config'
);
const
{
withServices
}
=
require
(
'../util/service-context'
);
const
uiConstants
=
require
(
'../ui-constants'
);
...
...
@@ -17,16 +19,44 @@ const SortMenu = require('./sort-menu');
const
SvgIcon
=
require
(
'./svg-icon'
);
const
UserMenu
=
require
(
'./user-menu'
);
/**
* Button for opening/closing the help panel
*/
function
HelpButton
({
onClick
})
{
const
isActive
=
useStore
(
store
=>
store
.
getState
().
sidebarPanels
.
activePanelName
===
uiConstants
.
PANEL_HELP
);
return
(
<
button
className
=
{
classnames
(
'top-bar__btn top-bar__help-btn'
,
{
'top-bar__btn--active'
:
isActive
,
})}
onClick
=
{
onClick
}
title
=
"Help"
aria
-
expanded
=
{
isActive
}
aria
-
pressed
=
{
isActive
}
>
<
SvgIcon
name
=
"help"
className
=
"top-bar__help-icon"
/>
<
/button
>
);
}
HelpButton
.
propTypes
=
{
/* callback */
onClick
:
propTypes
.
func
.
isRequired
,
};
/**
* The toolbar which appears at the top of the sidebar providing actions
* to switch groups, view account information, sort/filter annotations etc.
*/
function
TopBar
({
auth
,
bridge
,
isSidebar
,
onLogin
,
onLogout
,
onShowHelpPanel
,
onSignUp
,
settings
,
streamer
,
...
...
@@ -51,6 +81,19 @@ function TopBar({
togglePanelFn
(
uiConstants
.
PANEL_SHARE_ANNOTATIONS
);
};
/**
* Open the help panel, or, if a service callback is configured to handle
* help requests, fire a relevant event instead
*/
const
requestHelp
=
()
=>
{
const
service
=
serviceConfig
(
settings
)
||
{};
if
(
service
.
onHelpRequestProvided
)
{
bridge
.
call
(
bridgeEvents
.
HELP_REQUESTED
);
}
else
{
togglePanelFn
(
uiConstants
.
PANEL_HELP
);
}
};
const
loginControl
=
(
<
Fragment
>
{
auth
.
status
===
'unknown'
&&
(
...
...
@@ -82,14 +125,7 @@ function TopBar({
<
div
className
=
"top-bar__inner content"
>
<
StreamSearchInput
/>
<
div
className
=
"top-bar__expander"
/>
<
button
className
=
"top-bar__btn top-bar__help-btn"
onClick
=
{
onShowHelpPanel
}
title
=
"Help"
aria
-
label
=
"Help"
>
<
SvgIcon
name
=
"help"
className
=
"top-bar__help-icon"
/>
<
/button
>
<
HelpButton
onClick
=
{
requestHelp
}
/
>
{
loginControl
}
<
/div
>
)}
...
...
@@ -124,14 +160,7 @@ function TopBar({
<
SvgIcon
name
=
"share"
/>
<
/button
>
)}
<
button
className
=
"top-bar__btn top-bar__help-btn"
onClick
=
{
onShowHelpPanel
}
title
=
"Help"
aria
-
label
=
"Help"
>
<
SvgIcon
name
=
"help"
className
=
"top-bar__help-icon"
/>
<
/button
>
<
HelpButton
onClick
=
{
requestHelp
}
/
>
{
loginControl
}
<
/div
>
)}
...
...
@@ -152,16 +181,13 @@ TopBar.propTypes = {
username
:
propTypes
.
string
,
}),
bridge
:
propTypes
.
object
.
isRequired
,
/**
* Flag indicating whether the app is the sidebar or a top-level page.
*/
isSidebar
:
propTypes
.
bool
,
/**
* Callback invoked when user clicks "Help" button.
*/
onShowHelpPanel
:
propTypes
.
func
,
/**
* Callback invoked when user clicks "Login" button.
*/
...
...
@@ -178,6 +204,6 @@ TopBar.propTypes = {
streamer
:
propTypes
.
object
,
};
TopBar
.
injectedProps
=
[
'settings'
,
'streamer'
];
TopBar
.
injectedProps
=
[
'
bridge'
,
'
settings'
,
'streamer'
];
module
.
exports
=
withServices
(
TopBar
);
src/sidebar/components/tutorial.js
0 → 100644
View file @
ee714018
'use strict'
;
const
{
createElement
}
=
require
(
'preact'
);
const
propTypes
=
require
(
'prop-types'
);
const
{
withServices
}
=
require
(
'../util/service-context'
);
const
isThirdPartyService
=
require
(
'../util/is-third-party-service'
);
const
SvgIcon
=
require
(
'./svg-icon'
);
/**
* Subcomponent: an "instruction" within the tutorial step that includes an
* icon and a "command" associated with that icon. Encapsulating these together
* allows for styling to keep them from having a line break between them.
*/
function
TutorialInstruction
({
commandName
,
iconName
})
{
return
(
<
span
className
=
"tutorial__instruction"
>
<
SvgIcon
name
=
{
iconName
}
inline
=
{
true
}
className
=
"tutorial__icon"
/>
<
em
>
{
commandName
}
<
/em
>
<
/span
>
);
}
TutorialInstruction
.
propTypes
=
{
/* the name of the "command" the instruction represents, e.g. "Annotate" */
commandName
:
propTypes
.
string
.
isRequired
,
/* the name of the SVGIcon to display with this instruction */
iconName
:
propTypes
.
string
.
isRequired
,
};
/**
* Tutorial for using the sidebar app
*/
function
Tutorial
({
settings
})
{
const
canCreatePrivateGroups
=
!
isThirdPartyService
(
settings
);
return
(
<
ol
className
=
"tutorial__list"
>
<
li
className
=
"tutorial__item"
>
To
create
an
annotation
,
select
text
and
click
the
{
' '
}
<
TutorialInstruction
iconName
=
"annotate"
commandName
=
"Annotate"
/>
{
' '
}
button
.
<
/li
>
<
li
className
=
"tutorial__item"
>
To
create
a
highlight
(
<
a
href
=
"https://web.hypothes.is/help/why-are-highlights-private-by-default/"
target
=
"_blank"
rel
=
"noopener noreferrer"
>
visible
only
to
you
<
/a
>
),
select
text
and
click
the
{
' '
}
<
TutorialInstruction
iconName
=
"highlight"
commandName
=
"Highlight"
/>
{
' '
}
button
.
<
/li
>
{
canCreatePrivateGroups
&&
(
<
li
className
=
"tutorial__item"
>
To
annotate
in
a
private
group
,
select
the
group
from
the
groups
dropdown
.
Don
&
apos
;
t
see
your
group
?
Ask
the
group
creator
to
send
a
{
' '
}
<
a
href
=
"https://web.hypothes.is/help/how-to-join-a-private-group/"
target
=
"_blank"
rel
=
"noopener noreferrer"
>
join
link
<
/a
>
).
<
/li
>
)}
<
li
className
=
"tutorial__item"
>
To
reply
to
an
annotation
,
click
the
{
' '
}
<
TutorialInstruction
iconName
=
"reply"
commandName
=
"Reply"
/>
button
.
<
/li
>
<
/ol
>
);
}
Tutorial
.
propTypes
=
{
settings
:
propTypes
.
object
.
isRequired
,
};
Tutorial
.
injectedProps
=
[
'settings'
];
module
.
exports
=
withServices
(
Tutorial
);
src/sidebar/components/version-info.js
0 → 100644
View file @
ee714018
'use strict'
;
const
propTypes
=
require
(
'prop-types'
);
const
{
createElement
}
=
require
(
'preact'
);
/**
* Display current client version info
*/
function
VersionInfo
({
versionData
})
{
return
(
<
dl
className
=
"version-info"
>
<
dt
className
=
"version-info__key"
>
Version
<
/dt
>
<
dd
className
=
"version-info__value"
>
{
versionData
.
version
}
<
/dd
>
<
dt
className
=
"version-info__key"
>
User
Agent
<
/dt
>
<
dd
className
=
"version-info__value"
>
{
versionData
.
userAgent
}
<
/dd
>
<
dt
className
=
"version-info__key"
>
URL
<
/dt
>
<
dd
className
=
"version-info__value"
>
{
versionData
.
url
}
<
/dd
>
<
dt
className
=
"version-info__key"
>
Fingerprint
<
/dt
>
<
dd
className
=
"version-info__value"
>
{
versionData
.
fingerprint
}
<
/dd
>
<
dt
className
=
"version-info__key"
>
Account
<
/dt
>
<
dd
className
=
"version-info__value"
>
{
versionData
.
account
}
<
/dd
>
<
dt
className
=
"version-info__key"
>
Date
<
/dt
>
<
dd
className
=
"version-info__value"
>
{
versionData
.
timestamp
}
<
/dd
>
<
/dl
>
);
}
VersionInfo
.
propTypes
=
{
/**
* Object with version information
*
* @type {import('../util/version-info').VersionData}
*/
versionData
:
propTypes
.
object
.
isRequired
,
};
module
.
exports
=
VersionInfo
;
src/sidebar/index.js
View file @
ee714018
...
...
@@ -153,10 +153,9 @@ function startAngularApp(config) {
)
.
component
(
'excerpt'
,
require
(
'./components/excerpt'
))
.
component
(
'help
Link
'
,
wrapReactComponent
(
require
(
'./components/help-
link
'
))
'help
Panel
'
,
wrapReactComponent
(
require
(
'./components/help-
panel
'
))
)
.
component
(
'helpPanel'
,
require
(
'./components/help-panel'
))
.
component
(
'loggedOutMessage'
,
wrapReactComponent
(
require
(
'./components/logged-out-message'
))
...
...
@@ -194,7 +193,6 @@ function startAngularApp(config) {
'shareAnnotationsPanel'
,
wrapReactComponent
(
require
(
'./components/share-annotations-panel'
))
)
.
component
(
'sidebarTutorial'
,
require
(
'./components/sidebar-tutorial'
))
.
component
(
'streamContent'
,
require
(
'./components/stream-content'
))
.
component
(
'svgIcon'
,
wrapReactComponent
(
require
(
'./components/svg-icon'
)))
.
component
(
'tagEditor'
,
require
(
'./components/tag-editor'
))
...
...
src/sidebar/templates/help-panel.html
deleted
100644 → 0
View file @
518578a9
<div
class=
"help-panel"
>
<i
class=
"close h-icon-close"
role=
"button"
title=
"Close"
ng-click=
"vm.onClose()"
></i>
<header
class=
"help-panel-title"
>
Help
</header>
<div
class=
"help-panel-content"
>
<help-link
version=
"vm.version"
user-agent=
"vm.userAgent"
url=
"vm.url"
document-fingerprint=
"vm.documentFingerprint"
auth=
"vm.auth"
date-time=
"vm.dateTime"
>
</help-link>
if you have any questions or want to give us feedback.
You can also send
<a
class=
"help-panel-content__link"
href=
"https://web.hypothes.is/get-help/"
target=
"_blank"
>
a support ticket
</a>
or visit our
<a
class=
"help-panel-content__link"
href=
"https://web.hypothes.is/help/"
target=
"_blank"
>
help documents
</a>
.
</div>
<header
class=
"help-panel-title"
>
About this version
</header>
<dl
class=
"help-panel-content"
>
<dt
class=
"help-panel-content__key"
>
Version:
</dt>
<dd
class=
"help-panel-content__val"
>
{{ vm.version }}
</dd>
<dt
class=
"help-panel-content__key"
>
User agent:
</dt>
<dd
class=
"help-panel-content__val"
>
{{ vm.userAgent }}
</dd>
<div
ng-if=
"vm.url"
>
<dt
class=
"help-panel-content__key"
>
URL:
</dt>
<dd
class=
"help-panel-content__val"
>
{{ vm.url }}
</dd>
</div>
<div
ng-if=
"vm.documentFingerprint"
>
<dt
class=
"help-panel-content__key"
>
PDF fingerprint:
</dt>
<dd
class=
"help-panel-content__val"
>
{{ vm.documentFingerprint }}
</dd>
</div>
<div
ng-if=
"vm.auth.userid"
>
<dt
class=
"help-panel-content__key"
>
Username:
</dt>
<dd
class=
"help-panel-content__val"
>
{{ vm.auth.username }}
</dd>
</div>
<dt
class=
"help-panel-content__key"
>
Date:
</dt>
<dd
class=
"help-panel-content__val"
>
{{ vm.dateTime | date:'dd MMM yyyy HH:mm:ss Z' }}
</dd>
</div>
</div>
src/sidebar/templates/hypothesis-app.html
View file @
ee714018
...
...
@@ -4,16 +4,11 @@
on-login=
"vm.login()"
on-sign-up=
"vm.signUp()"
on-logout=
"vm.logout()"
on-show-help-panel=
"vm.showHelpPanel()"
is-sidebar=
"::vm.isSidebar"
>
</top-bar>
<div
class=
"content"
>
<sidebar-tutorial
ng-if=
"vm.isSidebar"
></sidebar-tutorial>
<help-panel
ng-if=
"vm.helpPanel.visible"
on-close=
"vm.helpPanel.visible = false"
auth=
"vm.auth"
>
</help-panel>
<help-panel
auth=
"vm.auth"
></help-panel>
<share-annotations-panel></share-annotations-panel>
<main
ng-view=
""
></main>
</div>
...
...
src/sidebar/templates/sidebar-tutorial.html
deleted
100644 → 0
View file @
518578a9
<div
class=
"sheet"
ng-if=
"vm.showSidebarTutorial() && !vm.isThemeClean"
>
<i
class=
"close h-icon-close"
role=
"button"
title=
"Close"
ng-click=
"vm.dismiss()"
></i>
<h1
class=
"sidebar-tutorial__header"
>
How to get started
</h1>
<ol
class=
"sidebar-tutorial__list"
>
<li
class=
"sidebar-tutorial__list-item"
>
<p
class=
"sidebar-tutorial__list-item-content"
>
To create an annotation, select text and click the
<i
class=
"h-icon-annotate"
></i>
button.
</p>
</li>
<li
class=
"sidebar-tutorial__list-item"
>
<p
class=
"sidebar-tutorial__list-item-content"
>
To add a note to the page you are viewing, click the
<i
class=
"h-icon-note"
></i>
button.
</p>
</li>
<li
class=
"sidebar-tutorial__list-item"
>
<p
class=
"sidebar-tutorial__list-item-content"
>
To create a highlight, select text and click the
<i
class=
"h-icon-highlight"
></i>
button.
</p>
</li>
<li
class=
"sidebar-tutorial__list-item"
>
<p
class=
"sidebar-tutorial__list-item-content"
>
To reply to an annotation, click the
<i
class=
"h-icon-annotation-reply"
></i>
<strong>
Reply
</strong>
link.
</p>
</li>
<li
class=
"sidebar-tutorial__list-item"
ng-if=
"vm.canSharePage()"
>
<p
class=
"sidebar-tutorial__list-item-content"
>
To share an annotated page, click the
<i
class=
"h-icon-annotation-share"
></i>
button at the top.
</p>
</li>
<li
class=
"sidebar-tutorial__list-item"
ng-if=
"vm.canCreatePrivateGroup()"
>
<p
class=
"sidebar-tutorial__list-item-content"
>
To create a private group, select
<strong>
Public
</strong>
,
open the dropdown, click
<strong>
+
New
group
</strong>
.
</p>
</li>
</ol>
</div>
<div
class=
"sheet sheet--is-theme-clean"
ng-if=
"vm.showSidebarTutorial() && vm.isThemeClean"
>
<i
class=
"close h-icon-close"
role=
"button"
title=
"Close"
ng-click=
"vm.dismiss()"
></i>
<h1
class=
"sidebar-tutorial__header sidebar-tutorial__header--is-theme-clean"
>
<i
class=
"h-icon-annotate sidebar-tutorial__header-annotate"
h-branding=
"accentColor"
></i>
Start annotating
</h1>
<ol
class=
"sidebar-tutorial__list"
>
<li
class=
"sidebar-tutorial__list-item sidebar-tutorial__list-item--is-theme-clean"
>
<div
class=
"sidebar-tutorial__list-item-content"
>
Select some text to
<span
class=
"sidebar-tutorial__list-item-annotate"
>
annotate
</span>
<svg-icon
class=
"sidebar-tutorial__list-item-cursor"
name=
"'cursor'"
></svg-icon>
or highlight.
</div>
</li>
<li
class=
"sidebar-tutorial__list-item sidebar-tutorial__list-item--is-theme-clean"
>
<div
class=
"sidebar-tutorial__list-item-content sidebar-tutorial__list-item-content--is-theme-clean"
>
Create page level notes
<span
class=
"sidebar-tutorial__list-item-new-note-btn"
h-branding=
"ctaBackgroundColor"
>
+ New note
</span>
</div>
</li>
<li
class=
"sidebar-tutorial__list-item sidebar-tutorial__list-item--is-theme-clean"
>
<div
class=
"sidebar-tutorial__list-item-content"
>
View annotations through your profile
<i
class=
"h-icon-account sidebar-tutorial__list-item-profile"
></i>
<i
class=
"h-icon-arrow-drop-down sidebar-tutorial__list-item-drop-down"
></i>
</div>
</li>
</ol>
</div>
src/sidebar/ui-constants.js
View file @
ee714018
...
...
@@ -5,6 +5,7 @@
*/
module
.
exports
=
{
PANEL_HELP
:
'help'
,
PANEL_SHARE_ANNOTATIONS
:
'shareGroupAnnotations'
,
TAB_ANNOTATIONS
:
'annotation'
,
TAB_NOTES
:
'note'
,
...
...
src/styles/sidebar/components/help-panel.scss
View file @
ee714018
.help-panel
{
@include
font-normal
;
background
:
$grey-3
;
margin-bottom
:
0
.72em
;
padding
:
$layout-h-margin
;
border-radius
:
2px
;
}
&
__sub-panel-title
{
margin
:
0
;
padding
:
0
.5em
;
text-align
:
center
;
font-size
:
1
.25em
;
font-weight
:
600
;
}
.help-panel-title
{
color
:
$grey-6
;
font-weight
:
bold
;
// Margin between top of the dialog and
// top of x-height of title should be ~15px.
margin-top
:
-5px
;
}
&
__content
{
padding
:
0
.5em
;
border-top
:
1px
solid
$grey-3
;
border-bottom
:
1px
solid
$grey-3
;
line-height
:
$normal-line-height
;
font-size
:
$normal-font-size
;
}
.help-panel-content
{
// Margin between bottom of ascent of title and
// top of x-height of content should be 20px.
margin-top
:
11px
;
}
&
__icon
{
width
:
12px
;
height
:
12px
;
}
.help-panel-content__key
{
width
:
100px
;
float
:
left
;
color
:
$grey-4
;
}
&
__footer
{
padding
:
0
.5em
0
;
display
:
flex
;
align-items
:
center
;
}
.help-panel-content__val
{
word-wrap
:
break-word
;
margin-left
:
100px
;
}
&
__sub-panel-link
{
display
:
flex
;
align-items
:
center
;
color
:
$brand
;
.help-panel-content
{
margin-top
:
10px
;
margin-bottom
:
15px
;
}
&
--right
{
margin-left
:
auto
;
}
&
-icon
{
margin
:
5px
;
width
:
12px
;
height
:
12px
;
}
}
&
-tabs
{
display
:
flex
;
align-items
:
center
;
&
__tab
{
flex
:
1
1
0px
;
margin-top
:
0
.5em
;
border-right
:
1px
solid
$grey-3
;
text-align
:
center
;
font-size
:
1
.25em
;
color
:
$grey-5
;
&
:last-of-type
{
border-right
:
none
;
}
}
&
__link
{
color
:
$grey-5
;
}
.help-panel-content__link
{
color
:
$grey-6
;
text-decoration
:
underline
;
&
:hover
{
text-decoration
:
underline
;
&
__icon
{
width
:
12px
;
height
:
12px
;
}
}
}
src/styles/sidebar/components/sidebar-panel.scss
View file @
ee714018
...
...
@@ -15,6 +15,16 @@
border-bottom-style
:
solid
;
}
&
__subheader
{
width
:
100%
;
text-align
:
center
;
padding
:
1em
0
.5em
;
border-bottom
:
1px
solid
$grey-3
;
font-size
:
1
.25em
;
font-weight
:
500
;
color
:
$grey-5
;
}
&
__title
{
color
:
$brand
;
font-size
:
$body2-font-size
;
...
...
@@ -44,7 +54,7 @@
}
&
__content
{
padding
:
0
.75
em
;
padding
-top
:
0
;
margin
:
1
em
;
margin
-top
:
0
;
}
}
src/styles/sidebar/components/sidebar-tutorial.scss
deleted
100644 → 0
View file @
518578a9
.sidebar-tutorial__header
{
color
:
$color-cardinal
;
font-size
:
$body1-font-size
;
font-weight
:
$title-font-weight
;
}
.sidebar-tutorial__header-annotate
{
position
:
relative
;
top
:
2px
;
}
.sidebar-tutorial__header--is-theme-clean
{
color
:
$gray-dark
;
font-size
:
16px
;
}
/* We want an ordered list in which the numbers are aligned with the text
above (not indented or dedented), and wrapped lines in list items are
aligned with the first character of the list item (not the number, i.e
they're indented):
This is a list:
1. This is a list item.
2. This is a long list item
that wraps.
3. This is another list item.
What's more, we want the numbers to be a different color to the text of the
list items, which means we need to use ::before content for them.
This appears to be much harder than you'd think.
The code below comes from this Stack Overflow answer:
http://stackoverflow.com/questions/10428720/how-to-keep-indent-for-second-line-in-ordered-lists-via-css/17515230#17515230
*/
.sidebar-tutorial__list
{
counter-reset
:
sidebar-tutorial__list
;
display
:
table
;
padding
:
0
;
}
.sidebar-tutorial__list-item
{
list-style
:
none
;
counter-increment
:
sidebar-tutorial__list
;
display
:
table-row
;
}
.
sidebar-tutorial__list-item
:
:
before
{
content
:
counter
(
sidebar-tutorial__list
)
'.'
;
display
:
table-cell
;
/* aha! */
text-align
:
right
;
padding-right
:
0
.3em
;
color
:
$gray-light
;
}
.sidebar-tutorial__list-item--is-theme-clean
{
font-size
:
14px
;
}
.
sidebar-tutorial__list-item--is-theme-clean
:
:
before
{
color
:
$gray-dark
;
}
.sidebar-tutorial__list-item-content
{
margin-top
:
1em
;
/* Put vertical space in-between list items, equal to the
space between normal paragraphs.
Note: This also puts the same amount of space above the
first list item (i.e. between the list and whatever's
above it). */
}
.sidebar-tutorial__list-item-annotate
{
background-color
:
$highlight-color-focus
;
padding
:
0px
3px
;
}
.sidebar-tutorial__list-item-new-note-btn
{
background-color
:
$color-dove-gray
;
border
:
none
;
border-radius
:
3px
;
color
:
#fff
;
font-weight
:
500
;
text-align
:
center
;
margin-left
:
2px
;
padding
:
2px
5px
;
}
.sidebar-tutorial__list-item-drop-down
{
margin-left
:
-5px
;
}
.sidebar-tutorial__list-item-cursor
{
position
:
relative
;
top
:
3px
;
margin-left
:
-10px
;
}
.sidebar-tutorial__list-item-cursor
svg
{
width
:
12px
;
height
:
17px
;
}
.sidebar-tutorial__list-item-content
{
margin-top
:
16px
;
}
.sidebar-tutorial__list-item-profile
{
margin-left
:
4px
;
}
src/styles/sidebar/components/tutorial.scss
0 → 100644
View file @
ee714018
.tutorial
{
&
__list
{
margin-top
:
1em
;
padding-left
:
2em
;
}
&
__item
{
margin
:
0
.5em
0
;
}
&
__icon
{
width
:
12px
;
height
:
12px
;
margin-right
:
1px
;
margin-bottom
:
-1px
;
// Pull the icon a little toward the baseline
color
:
$grey-5
;
}
&
__instruction
{
white-space
:
nowrap
;
// Keep icons and their associated names on the same line
}
}
src/styles/sidebar/components/version-info.scss
0 → 100644
View file @
ee714018
.version-info
{
margin-bottom
:
0
;
&
__key
{
float
:
left
;
width
:
8em
;
margin-bottom
:
0
.5em
;
padding-right
:
1em
;
line-height
:
1
.25em
;
text-align
:
right
;
font-weight
:
500
;
color
:
$grey-6
;
}
&
__value
{
margin-bottom
:
0
.5em
;
margin-left
:
8em
;
overflow-wrap
:
break-word
;
// Keep really long userids from overflowing
color
:
$grey-6
;
}
}
src/styles/sidebar/sidebar.scss
View file @
ee714018
...
...
@@ -45,13 +45,14 @@ $base-line-height: 20px;
@import
'./components/share-annotations-panel'
;
@import
'./components/search-input'
;
@import
'./components/sidebar-panel'
;
@import
'./components/sidebar-tutorial'
;
@import
'./components/svg-icon'
;
@import
'./components/spinner'
;
@import
'./components/tags-input'
;
@import
'./components/thread-list'
;
@import
'./components/tooltip'
;
@import
'./components/top-bar'
;
@import
'./components/tutorial'
;
@import
'./components/version-info'
;
// Top-level styles
// ----------------
...
...
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