Skip to content

Commit

Permalink
Split host / sidebar and clean up plugin loading
Browse files Browse the repository at this point in the history
  • Loading branch information
tilgovi committed Sep 12, 2015
1 parent 5a61a0a commit 16c8ddc
Show file tree
Hide file tree
Showing 15 changed files with 366 additions and 332 deletions.
56 changes: 28 additions & 28 deletions docs/hacking/customized-embedding.rst
Original file line number Diff line number Diff line change
@@ -1,44 +1,44 @@
Customized embedding
####################

To customize the plugins that are loaded, define a function ``window.hypothesisConfig``
which returns an options object::
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'},
BucketBar: {container: '.bucketbar-wrapper'}
constructor: Annotator.Sidebar,
app: 'https://example.com/custom_sidebar_iframe'
};
};

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 ``constructor`` property should be used to select an annotation
application. Four are provided: ``Annotator.Guest``, ``Annotator.Host``,
``Annotator.Sidebar`` and ``Annotator.PdfSidebar``.

The full range of possibilities here is still in need of documentation and we
would appreciate any help to improve that.
``Annotator.Guest`` expects to connect to an annotator widget running in a
different frame. Any number of instances can communicate with a single widget
in order to provide annotation of many frames.

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.
``Annotator.Host`` is an extended version of ``Annotator.Guest`` that will
instantiate an annotator widget by loading the location given by the ``app``
option in an iframe and appending it to the document.

The ``app`` property should be a url pointing to the HTML document that will be
embedded in the page.
``Annotator.Sidebar`` is an extended ``Annotator.Host`` that puts the widget
in a sidebar interface. It loads additional plugins that show a bar of bucket
indicators, each providing the ability to select a cluster of highlights, and a
toolbar that can be used to resize the widget and control other aspects of the
user interface.

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::
``Annotator.PdfSidebar`` is a custom version of ``Annotator.Sidebar`` with
defaults tailored for use in a PDF.js viewer.

It is possible to use a custom application 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.
window.hypothesisConfig = function () {
return {
constructor: Annotator.Guest
};
};
27 changes: 5 additions & 22 deletions h/static/scripts/annotator/guest.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ raf = require('raf')
scrollIntoView = require('scroll-into-view')

Annotator = require('annotator')
Annotator.Plugin.BucketBar = BucketBar = require('./plugin/bucket-bar')
Annotator.Plugin.CrossFrame = CrossFrame = require('./plugin/cross-frame')
$ = Annotator.$

highlighter = require('./highlighter')
Expand All @@ -33,11 +31,9 @@ module.exports = class Guest extends Annotator
".annotator-hl mouseover": "onHighlightMouseover"
".annotator-hl mouseout": "onHighlightMouseout"

# Plugin / Options configuration
options:
TextHighlights: {}
Document: {}
TextSelection: {}
clickToClose: true

# Anchoring module
anchoring: require('./anchoring/html')
Expand Down Expand Up @@ -131,24 +127,20 @@ module.exports = class Guest extends Annotator

_setupWrapper: ->
@wrapper = @element
if @options.clickToClose
@wrapper.on 'click', (event) =>
if !@selectedTargets?.length
this.hideFrame()
return this
this

# These methods aren't used in the iframe-hosted configuration of Annotator.
_setupDynamicStyle: -> this
_setupViewer: -> this
_setupEditor: -> this
_setupDocumentEvents: -> this
_setupDynamicStyle: -> this

destroy: ->
$('#annotator-dynamic-style').remove()

@adder.remove()

@wrapper.find('.annotator-hl').each ->
@element.find('.annotator-hl').each ->
$(this).contents().insertBefore(this)
$(this).remove()

Expand Down Expand Up @@ -331,7 +323,7 @@ module.exports = class Guest extends Annotator
else
# Show the adder button
@adder
.css(Annotator.Util.mousePosition(event, @wrapper[0]))
.css(Annotator.Util.mousePosition(event, @element[0]))
.show()

true
Expand Down Expand Up @@ -388,14 +380,6 @@ module.exports = class Guest extends Annotator

@visibleHighlights = shouldShowHighlights

# Open the sidebar
showFrame: ->
@crossframe?.call('open')

# Close the sidebar
hideFrame: ->
@crossframe?.call('back')

onAdderMouseup: (event) ->
event.preventDefault()
event.stopPropagation()
Expand All @@ -412,5 +396,4 @@ module.exports = class Guest extends Annotator
this.createHighlight()
when 'comment'
this.createAnnotation()
this.showFrame()
Annotator.Util.getGlobal().getSelection().removeAllRanges()
144 changes: 5 additions & 139 deletions h/static/scripts/annotator/host.coffee
Original file line number Diff line number Diff line change
@@ -1,170 +1,36 @@
raf = require('raf')

Annotator = require('annotator')
$ = Annotator.$

Hammer = require('hammerjs')

Guest = require('./guest')

# Minimum width to which the frame can be resized.
MIN_RESIZE = 280


module.exports = class Host extends Guest
renderFrame: null
gestureState: null

constructor: (element, options) ->
src = options.app
if options.firstRun
# Allow options.app to contain query string params.
src = src + (if '?' in src then '&' else '?') + 'firstrun'
options.app += (if '?' in options.app then '&' else '?') + 'firstrun'

# Create the iframe
app = $('<iframe></iframe>')
.attr('name', 'hyp_sidebar_frame')
.attr('seamless', '')
.attr('src', src)
.attr('src', options.app)

@frame = $('<div></div>')
.css('display', 'none')
.addClass('annotator-frame annotator-outer annotator-collapsed')
.addClass('annotator-frame annotator-outer')
.appendTo(element)

super
this._addCrossFrameListeners()

app.appendTo(@frame)

if options.firstRun
this.on 'panelReady', => this.showFrame(transition: false)

# Host frame dictates the toolbar options.
this.on 'panelReady', =>
# Initialize tool state.
this.setVisibleHighlights(!!options.showHighlights)

# Time to actually show the UI
# Show the UI
@frame.css('display', '')

if @plugins.BucketBar?
this._setupGestures()
@plugins.BucketBar.element.on 'click', (event) =>
if @frame.hasClass 'annotator-collapsed'
this.showFrame()

destroy: ->
@frame.remove()
super

showFrame: (options={transition: true}) ->
if options.transition
@frame.removeClass 'annotator-no-transition'
else
@frame.addClass 'annotator-no-transition'
@frame.css 'margin-left': "#{-1 * @frame.width()}px"
@frame.removeClass 'annotator-collapsed'

if @toolbar?
@toolbar.find('[name=sidebar-toggle]')
.removeClass('h-icon-chevron-left')
.addClass('h-icon-chevron-right')

hideFrame: ->
@frame.css 'margin-left': ''
@frame.removeClass 'annotator-no-transition'
@frame.addClass 'annotator-collapsed'

if @toolbar?
@toolbar.find('[name=sidebar-toggle]')
.removeClass('h-icon-chevron-right')
.addClass('h-icon-chevron-left')

_addCrossFrameListeners: ->
@crossframe.on('showFrame', this.showFrame.bind(this, null))
@crossframe.on('hideFrame', this.hideFrame.bind(this, null))

_initializeGestureState: ->
@gestureState =
initial: null
final: null

onPan: (event) =>
switch event.type
when 'panstart'
# Initialize the gesture state
this._initializeGestureState()
# Immadiate response
@frame.addClass 'annotator-no-transition'
# Escape iframe capture
@frame.css('pointer-events', 'none')
# Set origin margin
@gestureState.initial = parseInt(getComputedStyle(@frame[0]).marginLeft)

when 'panend'
# Re-enable transitions
@frame.removeClass 'annotator-no-transition'
# Re-enable iframe events
@frame.css('pointer-events', '')
# Snap open or closed
if @gestureState.final <= -MIN_RESIZE
this.showFrame()
else
this.hideFrame()
# Reset the gesture state
this._initializeGestureState()

when 'panleft', 'panright'
return unless @gestureState.initial?
# Compute new margin from delta and initial conditions
m = @gestureState.initial
d = event.deltaX
@gestureState.final = Math.min(Math.round(m + d), 0)
# Start updating
this._updateLayout()

onSwipe: (event) =>
switch event.type
when 'swipeleft'
this.showFrame()
when 'swiperight'
this.hideFrame()

_setupGestures: ->
$toggle = @toolbar.find('[name=sidebar-toggle]')

# Prevent any default gestures on the handle
$toggle.on('touchmove', (event) -> event.preventDefault())

# Set up the Hammer instance and handlers
mgr = new Hammer.Manager($toggle[0])
.on('panstart panend panleft panright', this.onPan)
.on('swipeleft swiperight', this.onSwipe)

# Set up the gesture recognition
pan = mgr.add(new Hammer.Pan({direction: Hammer.DIRECTION_HORIZONTAL}))
swipe = mgr.add(new Hammer.Swipe({direction: Hammer.DIRECTION_HORIZONTAL}))
swipe.recognizeWith(pan)

# Set up the initial state
this._initializeGestureState()

# Return this for chaining
this

# Schedule any changes needed to update the layout of the widget or page
# in response to interface changes.
_updateLayout: ->
# Only schedule one frame at a time
return if @renderFrame

# Schedule a frame
@renderFrame = raf =>
@renderFrame = null # Clear the schedule

# Process the resize gesture
if @gestureState.final isnt @gestureState.initial
m = @gestureState.final
w = -m
@frame.css('margin-left', "#{m}px")
if w >= MIN_RESIZE then @frame.css('width', "#{w}px")
42 changes: 18 additions & 24 deletions h/static/scripts/annotator/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,39 +10,31 @@ if (g.wgxpath) {
// Applications
Annotator.Guest = require('./guest');
Annotator.Host = require('./host');
Annotator.Sidebar = require('./sidebar');
Annotator.PdfSidebar = require('./pdf-sidebar');

// UI plugins
Annotator.Plugin.BucketBar = require('./plugin/bucket-bar');
Annotator.Plugin.Toolbar = require('./plugin/toolbar');

// Document type plugins
Annotator.Plugin.Pdf = require('./plugin/pdf');
require('../vendor/annotator.document'); // Does not export the plugin :(

// Selection plugins
Annotator.Plugin.TextSelection = require('./plugin/textselection');

// Cross-frame communication
Annotator.Plugin.CrossFrame = require('./plugin/cross-frame');
Annotator.Plugin.CrossFrame.Bridge = require('../bridge');
Annotator.Plugin.CrossFrame.AnnotationSync = require('../annotation-sync');
Annotator.Plugin.CrossFrame.Bridge = require('../bridge');
Annotator.Plugin.CrossFrame.Discovery = require('../discovery');

// Bucket bar
require('./plugin/bucket-bar');

// Toolbar
require('./plugin/toolbar');

// Creating selections
require('./plugin/textselection');

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', scrollables: ['body']},
Toolbar: {container: '.annotator-frame'}
app: jQuery('link[type="application/annotator+html"]').attr('href')
};

// Document metadata plugins
if (window.PDFViewerApplication) {
require('./plugin/pdf');
options.BucketBar.scrollables = ['#viewerContainer'];
options.PDF = {};
} else {
require('../vendor/annotator.document');
options.Document = {};
}

if (window.hasOwnProperty('hypothesisConfig')) {
if (typeof window.hypothesisConfig === 'function') {
extend(options, window.hypothesisConfig());
Expand All @@ -53,7 +45,9 @@ if (window.hasOwnProperty('hypothesisConfig')) {

Annotator.noConflict().$.noConflict(true)(function() {
'use strict';
var Klass = Annotator.Host;
var Klass = window.PDFViewerApplication ?
Annotator.PdfSidebar :
Annotator.Sidebar;
if (options.hasOwnProperty('constructor')) {
Klass = options.constructor;
delete options.constructor;
Expand Down

0 comments on commit 16c8ddc

Please sign in to comment.