Permalink
Comparing changes
Open a pull request
- 15 commits
- 23 files changed
- 0 commit comments
- 6 contributors
Unified
Split
Showing
with
365 additions
and 370 deletions.
- +30 −12 docs/hacking/customized-embedding.rst
- +1 −1 h/api_client/api_client.py
- +15 −5 h/atom_feed.py
- +6 −10 h/static/scripts/annotator/main.js
- +11 −6 h/static/scripts/directive/simple-search.coffee
- +5 −4 h/static/scripts/directive/test/thread-test.coffee
- +5 −5 h/static/scripts/directive/thread.coffee
- +25 −1 h/static/styles/annotations.scss
- +0 −16 h/static/styles/common.scss
- +6 −1 h/static/styles/forms.scss
- +2 −11 h/static/styles/simple-search.scss
- +9 −22 h/static/styles/threads.scss
- +23 −23 h/static/styles/topbar.scss
- BIN h/static/styles/vendor/fonts/h.eot
- +142 −166 h/static/styles/vendor/fonts/selection.json
- +7 −54 h/static/styles/vendor/icomoon.css
- +25 −26 h/templates/app.html
- +2 −2 h/templates/atom.xml
- +8 −1 h/templates/client/annotation.html
- +3 −3 h/templates/client/thread.html
- +37 −0 h/test/atom_feed_test.py
- +2 −0 requirements.txt
- +1 −1 setup.py
| @@ -1,26 +1,44 @@ | ||
| Customized embedding | ||
| #################### | ||
| By default, Hypothesis instantiates the ``Annotator.Host`` class defined in | ||
| the injected code loaded by ``embed.js``. It is possible to change this by | ||
| assigning an alternate constructor to ``window.hypothesisRole``. To customize | ||
| the plugins that are loaded, define a function ``window.hypothesisConfig`` which | ||
| returns an options object. This is then passed to the constructor as the | ||
| second argument:: | ||
| To customize the plugins that are loaded, define a function ``window.hypothesisConfig`` | ||
| which returns an options object:: | ||
| window.hypothesisConfig = function () { | ||
| return { | ||
| app: 'https://example.com/custom_sidebar_iframe', | ||
| Toolbar: {container: '.toolbar-wrapper'} | ||
| Toolbar: {container: '.toolbar-wrapper'}, | ||
| BucketBar: {container: '.bucketbar-wrapper'} | ||
| }; | ||
| }; | ||
| With the exception of ``app``, the properties for the options object are the | ||
| names of Annotator plugins and their values are the options passed to the | ||
| individual plugin constructors. | ||
| In the above example, the Toolbar will be attached to the element with the | ||
| ``.toolbar-wrapper`` class, and the BucketBar to the element with the ``.bucketbar-wrapper`` | ||
| class. | ||
| The full range of possibilities here is still in need of documentation and we | ||
| would appreciate any help to improve that. | ||
| With the exception of ``app`` and ``constructor``, the properties for the options object | ||
| are the names of Annotator plugins and their values are the options passed to the individual | ||
| plugin constructors. | ||
| The ``app`` property should be a url pointing to the HTML document that will be | ||
| embedded in the page. | ||
| The full range of possibilities here is still in need of documentation and we | ||
| would appreciate any help to improve that. | ||
| The ``constructor`` property should be used in when you want to annotate an iframe on a host | ||
| document. By instantiating the ``Annotator.Guest`` class inside the iframe you can capture | ||
| selection data from the frame which will be accessible by a host annotator in a parent document. | ||
| By default, Hypothesis instantiates the ``Annotator.Host`` class defined in the injected code | ||
| loaded by ``embed.js``. It is possible to change this by assigning an alternate ``constructor`` | ||
| in the options object returned by ``window.hypothesisConfig``. For example:: | ||
| window.hypothesisConfig = function () { | ||
| return { | ||
| constructor: Annotator.Guest | ||
| }; | ||
| }; | ||
| An Annotator Host can connect to multiple guests. |
| @@ -51,7 +51,7 @@ def __init__(self, base_url, timeout=None): | ||
| base_url = base_url + "/" | ||
| self.base_url = base_url | ||
| self.timeout = timeout or 0.2 | ||
| self.timeout = timeout or 2.0 | ||
| def get(self, path, params=None): | ||
| """Make a GET request to the Hypothesis API and return the response. | ||
| @@ -83,11 +83,17 @@ def _feed_entry_from_annotation( | ||
| def get_selection(annotation): | ||
| targets = annotation.get("target") | ||
| if targets: | ||
| for target in targets: | ||
| for selector in target["selector"]: | ||
| if "exact" in selector: | ||
| return selector["exact"] | ||
| if not isinstance(targets, list): | ||
| return | ||
| for target in targets: | ||
| selectors = target.get("selector") | ||
| if not isinstance(selectors, list): | ||
| continue | ||
| for selector in selectors: | ||
| if not isinstance(selector, dict): | ||
| continue | ||
| if "exact" in selector: | ||
| return selector["exact"] | ||
| content = "" | ||
| @@ -113,6 +119,10 @@ def get_selection(annotation): | ||
| entry["links"].append({"rel": "alternate", "type": "application/json", | ||
| "href": annotation_api_url(annotation)}) | ||
| for target in annotation.get('target', []): | ||
| entry["links"].append({"rel": "related", | ||
| "href": target.get('source')}) | ||
| return entry | ||
| @@ -47,22 +47,13 @@ require('./plugin/textquote'); | ||
| require('./plugin/textposition'); | ||
| require('./plugin/textrange'); | ||
| var Klass = Annotator.Host; | ||
| var docs = 'https://github.com/hypothesis/h/blob/master/README.rst#customized-embedding'; | ||
| var docs = 'https://h.readthedocs.org/en/latest/hacking/customized-embedding.html'; | ||
| var options = { | ||
| app: jQuery('link[type="application/annotator+html"]').attr('href'), | ||
| BucketBar: {container: '.annotator-frame'}, | ||
| Toolbar: {container: '.annotator-frame'} | ||
| }; | ||
| if (window.hasOwnProperty('hypothesisRole')) { | ||
| if (typeof window.hypothesisRole === 'function') { | ||
| Klass = window.hypothesisRole; | ||
| } else { | ||
| throw new TypeError('hypothesisRole must be a constructor function, see: ' + docs); | ||
| } | ||
| } | ||
| // Simple IE autodetect function | ||
| // See for example https://stackoverflow.com/questions/19999388/jquery-check-if-user-is-using-ie/21712356#21712356 | ||
| var ua = window.navigator.userAgent; | ||
| @@ -81,5 +72,10 @@ if (window.hasOwnProperty('hypothesisConfig')) { | ||
| } | ||
| Annotator.noConflict().$.noConflict(true)(function () { | ||
| var Klass = Annotator.Host; | ||
| if (options.hasOwnProperty('constructor')) { | ||
| Klass = options.constructor; | ||
| delete options.constructor; | ||
| } | ||
| window.annotator = new Klass(document.body, options); | ||
| }); | ||
| @@ -1,5 +1,10 @@ | ||
| module.exports = ['$http', '$parse', ($http, $parse) -> | ||
| link: (scope, elem, attr, ctrl) -> | ||
| button = elem.find('button') | ||
| input = elem.find('input') | ||
| button.on('click', -> input.focus()) | ||
| scope.reset = (event) -> | ||
| event.preventDefault() | ||
| scope.query = '' | ||
| @@ -30,12 +35,12 @@ module.exports = ['$http', '$parse', ($http, $parse) -> | ||
| <input class="simple-search-input" type="text" ng-model="searchtext" name="searchText" | ||
| placeholder="{{loading && 'Loading' || 'Search'}}…" | ||
| ng-disabled="loading" /> | ||
| <span class="simple-search-icon" ng-hide="loading"> | ||
| <i class="h-icon-search"></i> | ||
| </span> | ||
| <span class="simple-search-icon" ng-show="loading"> | ||
| <i class="spinner"></i> | ||
| </span> | ||
| <button class="simple-search-icon btn btn-clean" ng-hide="loading"> | ||
| <i class="h-icon-search btn-icon"></i> | ||
| </button> | ||
| <button class="simple-search-icon btn btn-clean" ng-show="loading" disabled> | ||
| <span class="btn-icon"><span class="spinner"></span></span> | ||
| </button> | ||
| </form> | ||
| ''' | ||
| ] | ||
| @@ -84,6 +84,9 @@ describe 'thread', -> | ||
| after = controller.collapsed | ||
| assert.equal(before, !after) | ||
| it 'defaults to collapsed if it is a top level annotation', -> | ||
| assert.isTrue(controller.collapsed) | ||
| it 'can accept an argument to force a particular state', -> | ||
| controller.toggleCollapsed(true) | ||
| assert.isTrue(controller.collapsed) | ||
| @@ -94,14 +97,12 @@ describe 'thread', -> | ||
| controller.toggleCollapsed(false) | ||
| assert.isFalse(controller.collapsed) | ||
| it 'does not allow uncollapsing the thread if there are no replies', -> | ||
| it 'allows collapsing the thread even if there are no replies', -> | ||
| count.withArgs('message').returns(1) | ||
| controller.toggleCollapsed() | ||
| assert.isTrue(controller.collapsed) | ||
| assert.isFalse(controller.collapsed) | ||
| controller.toggleCollapsed() | ||
| assert.isTrue(controller.collapsed) | ||
| controller.toggleCollapsed(false) | ||
| assert.isTrue(controller.collapsed) | ||
| describe '#shouldShowAsReply', -> | ||
| count = null | ||
| @@ -33,11 +33,6 @@ ThreadController = [ | ||
| !!value | ||
| else | ||
| not @collapsed | ||
| # We only allow uncollapsing of the thread if there are some replies to | ||
| # display. | ||
| if newval == false and this.numReplies() <= 0 | ||
| return | ||
| @collapsed = newval | ||
| ###* | ||
| @@ -199,6 +194,11 @@ module.exports = [ | ||
| ctrl.counter = counter | ||
| ctrl.filter = filter | ||
| # If annotation is a reply, it should be uncollapsed so that when | ||
| # shown, replies don't have to be individually expanded. | ||
| if ctrl.parent? | ||
| ctrl.collapsed = false | ||
| # Track the number of messages in the thread | ||
| if counter? | ||
| counter.count 'message', 1 | ||
| @@ -18,7 +18,10 @@ | ||
| } | ||
| .annotation-timestamp { | ||
| line-height: 2; | ||
| float: right; | ||
| font-size: .8em; | ||
| line-height: 1; | ||
| margin-top: (1 / (1 - .8)) * .1em; // scale up .1em offset to align baseline | ||
| color: $text-color; | ||
| &:hover { color: $link-color-hover; } | ||
| &:focus { outline: 0; } | ||
| @@ -162,3 +165,24 @@ privacy { | ||
| color: $text-color; | ||
| } | ||
| } | ||
| .annotation-collapsed-replies { | ||
| display: none; | ||
| } | ||
| .annotation.collapsed { | ||
| margin-bottom: 0; | ||
| .annotation-header { | ||
| margin: 0; | ||
| } | ||
| .annotation-body, .tags, .annotation-actions, .annotation-footer { | ||
| display: none; | ||
| } | ||
| .annotation-collapsed-replies { | ||
| display: inline; | ||
| } | ||
| } | ||
| @@ -110,22 +110,6 @@ html { | ||
| } | ||
| } | ||
| // Share this view ///////////////////// | ||
| .share-dialog-toggle { | ||
| top: 1px; | ||
| left: 8px; | ||
| position: relative; | ||
| cursor: pointer; | ||
| color: $gray-light; | ||
| float: left; | ||
| margin-right: 15px; | ||
| &:hover { | ||
| color: $gray-dark; | ||
| } | ||
| } | ||
| .share-links { | ||
| a { | ||
| font-size: 1.5em; | ||
| @@ -238,7 +238,8 @@ | ||
| } | ||
| .btn-clean { | ||
| &, &:focus, &:hover, &:active, &.js-hover, &.js-focus, &.js-active { | ||
| &, &:focus, &:hover, &:active, &[disabled], | ||
| &.js-hover, &.js-focus, &.js-active, &.js-disabled { | ||
| @include box-shadow(none); | ||
| padding-left: 0; | ||
| padding-right: 0; | ||
| @@ -253,6 +254,10 @@ | ||
| &:active, &.js-active { | ||
| color: $link-color-hover; | ||
| } | ||
| &[disabled], &.js-disabled { | ||
| color: $text-color; | ||
| } | ||
| } | ||
| // Positions the icon nicely within the button. | ||
| @@ -1,19 +1,11 @@ | ||
| @import "base.scss"; | ||
| @import "mixins/icons"; | ||
| .simple-search { | ||
| overflow: hidden; | ||
| } | ||
| .simple-search-form { | ||
| @include clearfix; | ||
| position: relative; | ||
| padding: 0 1.5385em; | ||
| color: $gray-darker; | ||
| } | ||
| .simple-search-form * { | ||
| line-height: inherit; | ||
| padding: 0 2em; | ||
| color: $gray-dark; | ||
| } | ||
| .simple-search-icon { | ||
| @@ -27,7 +19,6 @@ | ||
| } | ||
| input.simple-search-input { | ||
| float: left; | ||
| outline: none; | ||
| color: $text-color; | ||
| width: 100%; | ||
| @@ -1,5 +1,4 @@ | ||
| $thread-padding: 1em; | ||
| $threadexp-width: 1em; | ||
| .stream-list { | ||
| & > * { | ||
| @@ -16,14 +15,11 @@ $threadexp-width: 1em; | ||
| } | ||
| } | ||
| .thread-replies { | ||
| .thread:first-child { | ||
| margin-top: 0.5em; | ||
| } | ||
| .thread-replies .thread:first-child { | ||
| margin-top: 0.5em; | ||
| } | ||
| .thread { | ||
| @include pie-clearfix; | ||
| cursor: pointer; | ||
| position: relative; | ||
| @@ -51,27 +47,18 @@ $threadexp-width: 1em; | ||
| } | ||
| .threadexp { | ||
| background: $white; | ||
| color: $gray-light; | ||
| position: absolute; | ||
| left: -.7em; | ||
| width: 1.4em; | ||
| height: 1.4em; | ||
| top: 0; | ||
| left: -.7em; | ||
| font-size: 1.1em; | ||
| span { | ||
| position: absolute; | ||
| top: (1.4 - $threadexp-width) / 2; | ||
| left: (1.4 - $threadexp-width) / 2; | ||
| width: $threadexp-width; | ||
| height: $threadexp-width; | ||
| &:before { | ||
| font-size: $threadexp-width; | ||
| position: absolute; | ||
| top: 0; | ||
| left: 0; | ||
| } | ||
| background: $white; | ||
| color: $gray-light; | ||
| display: block; | ||
| line-height: inherit; | ||
| text-align: center; | ||
| } | ||
| } | ||
| } | ||
Oops, something went wrong.