2017-05-26 17:35:07 +00:00
|
|
|
|
/**
|
|
|
|
|
* @module renderer
|
|
|
|
|
*/
|
|
|
|
|
|
2017-07-28 17:32:46 +00:00
|
|
|
|
import wait from '../wait';
|
2018-03-07 19:53:09 +00:00
|
|
|
|
import pokeyMaskSVG from './pokey-mask.svg';
|
2018-03-08 20:38:23 +00:00
|
|
|
|
import { SIZES, createThumbnail } from './thumbnail';
|
2018-03-07 11:10:53 +00:00
|
|
|
|
import { previewTypes } from '../preview/model';
|
2018-03-27 19:10:48 +00:00
|
|
|
|
import { renderPreview } from './templates/preview/preview';
|
|
|
|
|
import { renderPagePreview } from './templates/pagePreview/pagePreview';
|
2017-07-28 17:32:46 +00:00
|
|
|
|
|
2018-03-19 19:39:41 +00:00
|
|
|
|
const mw = window.mediaWiki,
|
2017-02-14 20:17:32 +00:00
|
|
|
|
$ = jQuery,
|
2018-03-08 20:38:23 +00:00
|
|
|
|
$window = $( window ),
|
|
|
|
|
landscapePopupWidth = 450,
|
|
|
|
|
portraitPopupWidth = 320,
|
|
|
|
|
pokeySize = 8; // Height of the pokey.;
|
2017-02-14 20:17:32 +00:00
|
|
|
|
|
|
|
|
|
/**
|
2018-03-07 20:35:40 +00:00
|
|
|
|
* Extracted from `mw.popups.createSVGMasks`. This is just an SVG mask to point
|
|
|
|
|
* or "poke" at the link that's hovered over. The "pokey" appears to be cut out
|
|
|
|
|
* of the image itself:
|
2018-04-13 17:29:57 +00:00
|
|
|
|
* _______ link
|
|
|
|
|
* | | _/\_____ _/\____ <-- Pokey pointing at link
|
|
|
|
|
* | :-] | + |xxxxxxx = | :-] |
|
|
|
|
|
* |_______| |xxxxxxx |_______|
|
|
|
|
|
* :
|
|
|
|
|
* Thumbnail Pokey Page preview
|
|
|
|
|
* image clip-path bubble w/ pokey
|
2018-03-07 20:35:40 +00:00
|
|
|
|
*
|
|
|
|
|
* SVG masks are used in place of CSS masks for browser support issues (see
|
|
|
|
|
* https://caniuse.com/#feat=css-masks).
|
|
|
|
|
*
|
2017-04-27 19:55:43 +00:00
|
|
|
|
* @private
|
|
|
|
|
* @param {Object} container DOM object to which pokey masks are appended
|
2017-02-14 20:17:32 +00:00
|
|
|
|
*/
|
2017-07-28 17:32:46 +00:00
|
|
|
|
export function createPokeyMasks( container ) {
|
2017-02-14 20:17:32 +00:00
|
|
|
|
$( '<div>' )
|
|
|
|
|
.attr( 'id', 'mwe-popups-svg' )
|
2018-03-07 19:53:09 +00:00
|
|
|
|
.html( pokeyMaskSVG )
|
2017-04-27 19:55:43 +00:00
|
|
|
|
.appendTo( container );
|
2017-02-14 20:17:32 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Initializes the renderer.
|
|
|
|
|
*/
|
2017-07-28 17:32:46 +00:00
|
|
|
|
export function init() {
|
2017-04-27 19:55:43 +00:00
|
|
|
|
createPokeyMasks( document.body );
|
2017-02-14 20:17:32 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* The model of how a view is rendered, which is constructed from a response
|
|
|
|
|
* from the gateway.
|
|
|
|
|
*
|
|
|
|
|
* TODO: Rename `isTall` to `isPortrait`.
|
|
|
|
|
*
|
|
|
|
|
* @typedef {Object} ext.popups.Preview
|
|
|
|
|
* @property {jQuery} el
|
|
|
|
|
* @property {Boolean} hasThumbnail
|
|
|
|
|
* @property {Object} thumbnail
|
|
|
|
|
* @property {Boolean} isTall Sugar around
|
|
|
|
|
* `preview.hasThumbnail && thumbnail.isTall`
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/**
|
2017-06-15 11:35:19 +00:00
|
|
|
|
* Renders a preview given data from the {@link gateway Gateway}.
|
2017-02-14 20:17:32 +00:00
|
|
|
|
* The preview is rendered and added to the DOM but will remain hidden until
|
|
|
|
|
* the `show` method is called.
|
|
|
|
|
*
|
|
|
|
|
* Previews are rendered at:
|
|
|
|
|
*
|
|
|
|
|
* # The position of the mouse when the user dwells on the link with their
|
|
|
|
|
* mouse.
|
|
|
|
|
* # The centermost point of the link when the user dwells on the link with
|
2018-01-11 03:23:28 +00:00
|
|
|
|
* their keyboard or other assistive device.
|
2017-02-14 20:17:32 +00:00
|
|
|
|
*
|
|
|
|
|
* Since the content of the preview doesn't change but its position might, we
|
|
|
|
|
* distinguish between "rendering" - generating HTML from a MediaWiki API
|
|
|
|
|
* response - and "showing/hiding" - positioning the layout and changing its
|
|
|
|
|
* orientation, if necessary.
|
|
|
|
|
*
|
|
|
|
|
* @param {ext.popups.PreviewModel} model
|
|
|
|
|
* @return {ext.popups.Preview}
|
|
|
|
|
*/
|
2017-07-28 17:32:46 +00:00
|
|
|
|
export function render( model ) {
|
2018-03-07 11:10:53 +00:00
|
|
|
|
|
2018-03-19 19:39:41 +00:00
|
|
|
|
const preview = createPreviewWithType( model );
|
2017-02-14 20:17:32 +00:00
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Shows the preview given an event representing the user's interaction
|
|
|
|
|
* with the active link, e.g. an instance of
|
|
|
|
|
* [MouseEvent](https://developer.mozilla.org/en/docs/Web/API/MouseEvent).
|
|
|
|
|
*
|
|
|
|
|
* See `show` for more detail.
|
|
|
|
|
*
|
|
|
|
|
* @param {Event} event
|
|
|
|
|
* @param {Object} boundActions The
|
|
|
|
|
* [bound action creators](http://redux.js.org/docs/api/bindActionCreators.html)
|
|
|
|
|
* that were (likely) created in [boot.js](./boot.js).
|
2017-04-10 17:28:24 +00:00
|
|
|
|
* @param {String} token The unique token representing the link interaction
|
|
|
|
|
* that resulted in showing the preview
|
2017-02-14 20:17:32 +00:00
|
|
|
|
* @return {jQuery.Promise}
|
|
|
|
|
*/
|
2018-03-14 22:04:59 +00:00
|
|
|
|
show( event, boundActions, token ) {
|
2017-05-08 16:51:42 +00:00
|
|
|
|
return show(
|
|
|
|
|
preview, event, $( event.target ), boundActions, token,
|
|
|
|
|
document.body
|
|
|
|
|
);
|
2016-11-21 12:07:23 +00:00
|
|
|
|
},
|
|
|
|
|
|
2017-02-14 20:17:32 +00:00
|
|
|
|
/**
|
|
|
|
|
* Hides the preview.
|
|
|
|
|
*
|
|
|
|
|
* See `hide` for more detail.
|
|
|
|
|
*
|
|
|
|
|
* @return {jQuery.Promise}
|
|
|
|
|
*/
|
2018-03-14 22:04:59 +00:00
|
|
|
|
hide() {
|
2017-02-14 20:17:32 +00:00
|
|
|
|
return hide( preview );
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
2018-03-07 11:10:53 +00:00
|
|
|
|
/**
|
|
|
|
|
* Creates an instance of a Preview based on
|
|
|
|
|
* the type property of the PreviewModel
|
|
|
|
|
*
|
|
|
|
|
* @param {ext.popups.PreviewModel} model
|
|
|
|
|
* @return {ext.popups.Preview}
|
|
|
|
|
*/
|
|
|
|
|
export function createPreviewWithType( model ) {
|
|
|
|
|
switch ( model.type ) {
|
|
|
|
|
case previewTypes.TYPE_PAGE:
|
|
|
|
|
return createPagePreview( model );
|
|
|
|
|
case previewTypes.TYPE_DISAMBIGUATION:
|
|
|
|
|
return createDisambiguationPreview( model );
|
|
|
|
|
default:
|
|
|
|
|
return createEmptyPreview( model );
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-02-14 20:17:32 +00:00
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Creates an instance of the DTO backing a preview.
|
|
|
|
|
*
|
|
|
|
|
* @param {ext.popups.PreviewModel} model
|
|
|
|
|
* @return {ext.popups.Preview}
|
|
|
|
|
*/
|
2018-03-07 11:10:53 +00:00
|
|
|
|
export function createPagePreview( model ) {
|
2018-03-19 19:39:41 +00:00
|
|
|
|
const thumbnail = createThumbnail( model.thumbnail ),
|
2017-02-14 20:17:32 +00:00
|
|
|
|
hasThumbnail = thumbnail !== null,
|
2018-03-19 19:39:41 +00:00
|
|
|
|
extract = model.extract;
|
2017-02-14 20:17:32 +00:00
|
|
|
|
|
2018-03-19 19:39:41 +00:00
|
|
|
|
const $el = $( $.parseHTML( renderPagePreview( model, hasThumbnail ) ) );
|
2017-02-14 20:17:32 +00:00
|
|
|
|
|
|
|
|
|
if ( hasThumbnail ) {
|
|
|
|
|
$el.find( '.mwe-popups-discreet' ).append( thumbnail.el );
|
2017-01-26 01:26:16 +00:00
|
|
|
|
}
|
2017-06-08 00:58:30 +00:00
|
|
|
|
if ( extract ) {
|
2017-02-14 20:17:32 +00:00
|
|
|
|
$el.find( '.mwe-popups-extract' ).append( extract );
|
2017-01-26 01:26:16 +00:00
|
|
|
|
}
|
2016-11-21 12:07:23 +00:00
|
|
|
|
|
2017-02-14 20:17:32 +00:00
|
|
|
|
return {
|
|
|
|
|
el: $el,
|
2018-03-14 19:44:22 +00:00
|
|
|
|
hasThumbnail,
|
|
|
|
|
thumbnail,
|
2017-02-14 20:17:32 +00:00
|
|
|
|
isTall: hasThumbnail && thumbnail.isTall
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Creates an instance of the DTO backing a preview. In this case the DTO
|
|
|
|
|
* represents a generic preview, which covers the following scenarios:
|
|
|
|
|
*
|
|
|
|
|
* * The page doesn't exist, i.e. the user hovered over a redlink or a
|
|
|
|
|
* redirect to a page that doesn't exist.
|
|
|
|
|
* * The page doesn't have a viable extract.
|
|
|
|
|
*
|
|
|
|
|
* @param {ext.popups.PreviewModel} model
|
|
|
|
|
* @return {ext.popups.Preview}
|
|
|
|
|
*/
|
2017-07-28 17:32:46 +00:00
|
|
|
|
export function createEmptyPreview( model ) {
|
2018-03-19 19:39:41 +00:00
|
|
|
|
const showTitle = false,
|
2018-03-13 22:03:49 +00:00
|
|
|
|
extractMsg = mw.msg( 'popups-preview-no-preview' ),
|
2018-03-19 19:39:41 +00:00
|
|
|
|
linkMsg = mw.msg( 'popups-preview-footer-read' );
|
2017-02-14 20:17:32 +00:00
|
|
|
|
|
2018-03-19 19:39:41 +00:00
|
|
|
|
const $el = $(
|
2018-03-13 22:03:49 +00:00
|
|
|
|
$.parseHTML( renderPreview( model, showTitle, extractMsg, linkMsg ) )
|
|
|
|
|
);
|
2018-03-07 11:10:53 +00:00
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
el: $el,
|
|
|
|
|
hasThumbnail: false,
|
|
|
|
|
isTall: false
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Creates an instance of the disambiguation preview.
|
|
|
|
|
*
|
|
|
|
|
* @param {ext.popups.PreviewModel} model
|
|
|
|
|
* @return {ext.popups.Preview}
|
|
|
|
|
*/
|
|
|
|
|
export function createDisambiguationPreview( model ) {
|
2018-03-19 19:39:41 +00:00
|
|
|
|
const showTitle = true,
|
2018-03-13 22:03:49 +00:00
|
|
|
|
extractMsg = mw.msg( 'popups-preview-disambiguation' ),
|
2018-03-19 19:39:41 +00:00
|
|
|
|
linkMsg = mw.msg( 'popups-preview-disambiguation-link' );
|
2018-03-07 11:10:53 +00:00
|
|
|
|
|
2018-03-19 19:39:41 +00:00
|
|
|
|
const $el = $(
|
2018-03-13 22:03:49 +00:00
|
|
|
|
$.parseHTML( renderPreview( model, showTitle, extractMsg, linkMsg ) )
|
|
|
|
|
);
|
2017-02-14 20:17:32 +00:00
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
el: $el,
|
|
|
|
|
hasThumbnail: false,
|
|
|
|
|
isTall: false
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Shows the preview.
|
|
|
|
|
*
|
|
|
|
|
* Extracted from `mw.popups.render.openPopup`.
|
|
|
|
|
*
|
|
|
|
|
* TODO: From the perspective of the client, there's no need to distinguish
|
2018-03-13 22:03:49 +00:00
|
|
|
|
* between rendering and showing a preview. Merge #render and Preview#show.
|
2017-02-14 20:17:32 +00:00
|
|
|
|
*
|
|
|
|
|
* @param {ext.popups.Preview} preview
|
|
|
|
|
* @param {Event} event
|
2017-05-08 16:51:42 +00:00
|
|
|
|
* @param {jQuery} $link event target
|
2017-02-14 20:17:32 +00:00
|
|
|
|
* @param {ext.popups.PreviewBehavior} behavior
|
2017-04-10 17:28:24 +00:00
|
|
|
|
* @param {String} token
|
2017-05-08 16:51:42 +00:00
|
|
|
|
* @param {Object} container DOM object to which pokey masks are appended
|
2017-02-14 20:17:32 +00:00
|
|
|
|
* @return {jQuery.Promise} A promise that resolves when the promise has faded
|
|
|
|
|
* in
|
|
|
|
|
*/
|
2018-02-08 22:11:44 +00:00
|
|
|
|
export function show( preview, event, $link, behavior,
|
|
|
|
|
token, container
|
|
|
|
|
) {
|
2018-03-19 19:39:41 +00:00
|
|
|
|
const layout = createLayout(
|
2017-05-08 16:51:42 +00:00
|
|
|
|
preview.isTall,
|
|
|
|
|
{
|
|
|
|
|
pageX: event.pageX,
|
|
|
|
|
pageY: event.pageY,
|
|
|
|
|
clientY: event.clientY
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
clientRects: $link.get( 0 ).getClientRects(),
|
|
|
|
|
offset: $link.offset(),
|
|
|
|
|
width: $link.width(),
|
|
|
|
|
height: $link.height()
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
scrollTop: $window.scrollTop(),
|
|
|
|
|
width: $window.width(),
|
|
|
|
|
height: $window.height()
|
|
|
|
|
},
|
2018-03-08 20:38:23 +00:00
|
|
|
|
pokeySize
|
2017-05-08 16:51:42 +00:00
|
|
|
|
);
|
2017-02-14 20:17:32 +00:00
|
|
|
|
|
2017-05-08 16:51:42 +00:00
|
|
|
|
preview.el.appendTo( container );
|
2017-02-14 20:17:32 +00:00
|
|
|
|
|
2017-05-05 19:36:04 +00:00
|
|
|
|
layoutPreview(
|
|
|
|
|
preview, layout, getClasses( preview, layout ),
|
2018-03-08 20:38:23 +00:00
|
|
|
|
SIZES.landscapeImage.h, pokeySize
|
2017-05-05 19:36:04 +00:00
|
|
|
|
);
|
2017-02-14 20:17:32 +00:00
|
|
|
|
|
2017-03-22 10:45:37 +00:00
|
|
|
|
preview.el.show();
|
|
|
|
|
|
|
|
|
|
return wait( 200 )
|
2018-03-14 23:50:09 +00:00
|
|
|
|
.then( () => {
|
2017-03-22 10:45:37 +00:00
|
|
|
|
bindBehavior( preview, behavior );
|
|
|
|
|
} )
|
2018-03-14 23:50:09 +00:00
|
|
|
|
.then( () => {
|
2017-04-10 17:28:24 +00:00
|
|
|
|
behavior.previewShow( token );
|
|
|
|
|
} );
|
2017-03-22 10:45:37 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Binds the behavior to the interactive elements of the preview.
|
|
|
|
|
*
|
|
|
|
|
* @param {ext.popups.Preview} preview
|
|
|
|
|
* @param {ext.popups.PreviewBehavior} behavior
|
|
|
|
|
*/
|
2017-07-28 17:32:46 +00:00
|
|
|
|
export function bindBehavior( preview, behavior ) {
|
2017-12-05 22:21:42 +00:00
|
|
|
|
preview.el.on( 'mouseenter', behavior.previewDwell )
|
|
|
|
|
.on( 'mouseleave', behavior.previewAbandon );
|
2017-02-14 20:17:32 +00:00
|
|
|
|
|
2017-03-22 14:54:04 +00:00
|
|
|
|
preview.el.click( behavior.click );
|
|
|
|
|
|
2017-02-14 20:17:32 +00:00
|
|
|
|
preview.el.find( '.mwe-popups-settings-icon' )
|
|
|
|
|
.attr( 'href', behavior.settingsUrl )
|
2018-03-14 23:50:09 +00:00
|
|
|
|
.click( ( event ) => {
|
2017-03-22 14:54:04 +00:00
|
|
|
|
event.stopPropagation();
|
|
|
|
|
|
2017-04-05 23:56:12 +00:00
|
|
|
|
behavior.showSettings( event );
|
2017-03-22 14:54:04 +00:00
|
|
|
|
} );
|
2017-02-14 20:17:32 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Extracted from `mw.popups.render.closePopup`.
|
|
|
|
|
*
|
|
|
|
|
* @param {ext.popups.Preview} preview
|
|
|
|
|
* @return {jQuery.Promise} A promise that resolves when the preview has faded
|
|
|
|
|
* out
|
|
|
|
|
*/
|
2017-07-28 17:32:46 +00:00
|
|
|
|
export function hide( preview ) {
|
2017-02-14 20:17:32 +00:00
|
|
|
|
// FIXME: This method clearly needs access to the layout of the preview.
|
2018-03-19 19:39:41 +00:00
|
|
|
|
const fadeInClass = ( preview.el.hasClass( 'mwe-popups-fade-in-up' ) ) ?
|
2017-02-14 20:17:32 +00:00
|
|
|
|
'mwe-popups-fade-in-up' :
|
|
|
|
|
'mwe-popups-fade-in-down';
|
|
|
|
|
|
2018-03-19 19:39:41 +00:00
|
|
|
|
const fadeOutClass = ( fadeInClass === 'mwe-popups-fade-in-up' ) ?
|
2017-02-14 20:17:32 +00:00
|
|
|
|
'mwe-popups-fade-out-down' :
|
|
|
|
|
'mwe-popups-fade-out-up';
|
|
|
|
|
|
|
|
|
|
preview.el
|
|
|
|
|
.removeClass( fadeInClass )
|
|
|
|
|
.addClass( fadeOutClass );
|
|
|
|
|
|
2018-03-14 23:50:09 +00:00
|
|
|
|
return wait( 150 ).then( () => {
|
2017-02-14 20:17:32 +00:00
|
|
|
|
preview.el.remove();
|
|
|
|
|
} );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Represents the layout of a preview, which consists of a position (`offset`)
|
|
|
|
|
* and whether or not the preview should be flipped horizontally or
|
|
|
|
|
* vertically (`flippedX` and `flippedY` respectively).
|
|
|
|
|
*
|
|
|
|
|
* @typedef {Object} ext.popups.PreviewLayout
|
|
|
|
|
* @property {Object} offset
|
2017-05-05 15:57:52 +00:00
|
|
|
|
* @property {number} offset.top
|
|
|
|
|
* @property {number} offset.left
|
2017-02-14 20:17:32 +00:00
|
|
|
|
* @property {Boolean} flippedX
|
|
|
|
|
* @property {Boolean} flippedY
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/**
|
2018-04-11 20:40:20 +00:00
|
|
|
|
* @param {Boolean} isPreviewTall
|
2018-01-18 18:48:16 +00:00
|
|
|
|
* @param {Object} eventData Data related to the event that triggered showing
|
|
|
|
|
* a popup
|
2017-05-05 15:57:52 +00:00
|
|
|
|
* @param {number} eventData.pageX
|
|
|
|
|
* @param {number} eventData.pageY
|
|
|
|
|
* @param {number} eventData.clientY
|
2018-01-18 18:48:16 +00:00
|
|
|
|
* @param {Object} linkData Data related to the link that’s used for showing
|
|
|
|
|
* a popup
|
|
|
|
|
* @param {ClientRectList} linkData.clientRects list of rectangles defined by
|
|
|
|
|
* four edges
|
2017-05-05 15:57:52 +00:00
|
|
|
|
* @param {Object} linkData.offset
|
|
|
|
|
* @param {number} linkData.width
|
|
|
|
|
* @param {number} linkData.height
|
|
|
|
|
* @param {Object} windowData Data related to the window
|
|
|
|
|
* @param {number} windowData.scrollTop
|
|
|
|
|
* @param {number} windowData.width
|
|
|
|
|
* @param {number} windowData.height
|
|
|
|
|
* @param {number} pokeySize Space reserved for the pokey
|
2017-02-14 20:17:32 +00:00
|
|
|
|
* @return {ext.popups.PreviewLayout}
|
|
|
|
|
*/
|
2018-01-18 18:48:16 +00:00
|
|
|
|
export function createLayout(
|
|
|
|
|
isPreviewTall, eventData, linkData, windowData, pokeySize
|
|
|
|
|
) {
|
2018-03-15 16:30:11 +00:00
|
|
|
|
let flippedX = false,
|
2017-02-14 20:17:32 +00:00
|
|
|
|
flippedY = false,
|
2017-10-09 14:56:15 +00:00
|
|
|
|
offsetTop = eventData.pageY ?
|
|
|
|
|
// If it was a mouse event, position according to mouse
|
2017-02-14 20:17:32 +00:00
|
|
|
|
// Since client rectangles are relative to the viewport,
|
|
|
|
|
// take scroll position into account.
|
|
|
|
|
getClosestYPosition(
|
2017-05-05 15:57:52 +00:00
|
|
|
|
eventData.pageY - windowData.scrollTop,
|
|
|
|
|
linkData.clientRects,
|
2017-02-14 20:17:32 +00:00
|
|
|
|
false
|
2017-05-05 15:57:52 +00:00
|
|
|
|
) + windowData.scrollTop + pokeySize :
|
2017-02-14 20:17:32 +00:00
|
|
|
|
// Position according to link position or size
|
2017-05-05 15:57:52 +00:00
|
|
|
|
linkData.offset.top + linkData.height + pokeySize,
|
2018-03-19 19:39:41 +00:00
|
|
|
|
offsetLeft = eventData.pageX ? eventData.pageX : linkData.offset.left;
|
|
|
|
|
const clientTop = eventData.clientY ? eventData.clientY : offsetTop;
|
2017-02-14 20:17:32 +00:00
|
|
|
|
|
|
|
|
|
// X Flip
|
2017-05-05 15:57:52 +00:00
|
|
|
|
if ( offsetLeft > ( windowData.width / 2 ) ) {
|
|
|
|
|
offsetLeft += ( !eventData.pageX ) ? linkData.width : 0;
|
|
|
|
|
offsetLeft -= !isPreviewTall ?
|
2018-03-08 20:38:23 +00:00
|
|
|
|
portraitPopupWidth :
|
|
|
|
|
landscapePopupWidth;
|
2017-02-14 20:17:32 +00:00
|
|
|
|
flippedX = true;
|
2016-11-21 12:07:23 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-05-05 15:57:52 +00:00
|
|
|
|
if ( eventData.pageX ) {
|
2017-02-14 20:17:32 +00:00
|
|
|
|
offsetLeft += ( flippedX ) ? 20 : -20;
|
2016-11-21 12:07:23 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-02-14 20:17:32 +00:00
|
|
|
|
// Y Flip
|
2017-05-05 15:57:52 +00:00
|
|
|
|
if ( clientTop > ( windowData.height / 2 ) ) {
|
2017-02-14 20:17:32 +00:00
|
|
|
|
flippedY = true;
|
|
|
|
|
|
|
|
|
|
// Mirror the positioning of the preview when there's no "Y flip": rest
|
|
|
|
|
// the pokey on the edge of the link's bounding rectangle. In this case
|
|
|
|
|
// the edge is the top-most.
|
2017-05-05 15:57:52 +00:00
|
|
|
|
offsetTop = linkData.offset.top;
|
2017-02-14 20:17:32 +00:00
|
|
|
|
|
|
|
|
|
// Change the Y position to the top of the link
|
2017-05-05 15:57:52 +00:00
|
|
|
|
if ( eventData.pageY ) {
|
2017-02-14 20:17:32 +00:00
|
|
|
|
// Since client rectangles are relative to the viewport,
|
|
|
|
|
// take scroll position into account.
|
|
|
|
|
offsetTop = getClosestYPosition(
|
2017-05-05 15:57:52 +00:00
|
|
|
|
eventData.pageY - windowData.scrollTop,
|
|
|
|
|
linkData.clientRects,
|
2017-02-14 20:17:32 +00:00
|
|
|
|
true
|
2017-05-05 15:57:52 +00:00
|
|
|
|
) + windowData.scrollTop;
|
2016-11-21 12:07:23 +00:00
|
|
|
|
}
|
2017-04-20 15:30:54 +00:00
|
|
|
|
|
2017-05-05 15:57:52 +00:00
|
|
|
|
offsetTop -= pokeySize;
|
2016-11-21 12:07:23 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-02-14 20:17:32 +00:00
|
|
|
|
return {
|
|
|
|
|
offset: {
|
|
|
|
|
top: offsetTop,
|
|
|
|
|
left: offsetLeft
|
|
|
|
|
},
|
2018-03-14 19:44:22 +00:00
|
|
|
|
flippedX,
|
|
|
|
|
flippedY
|
2017-02-14 20:17:32 +00:00
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Generates a list of declarative CSS classes that represent the layout of
|
|
|
|
|
* the preview.
|
|
|
|
|
*
|
|
|
|
|
* @param {ext.popups.Preview} preview
|
|
|
|
|
* @param {ext.popups.PreviewLayout} layout
|
|
|
|
|
* @return {String[]}
|
|
|
|
|
*/
|
2017-07-28 17:32:46 +00:00
|
|
|
|
export function getClasses( preview, layout ) {
|
2018-03-19 19:39:41 +00:00
|
|
|
|
const classes = [];
|
2017-02-14 20:17:32 +00:00
|
|
|
|
|
|
|
|
|
if ( layout.flippedY ) {
|
|
|
|
|
classes.push( 'mwe-popups-fade-in-down' );
|
|
|
|
|
} else {
|
|
|
|
|
classes.push( 'mwe-popups-fade-in-up' );
|
2016-11-21 12:07:23 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-02-14 20:17:32 +00:00
|
|
|
|
if ( layout.flippedY && layout.flippedX ) {
|
2018-04-23 21:23:17 +00:00
|
|
|
|
classes.push( 'flipped-x-y' );
|
2018-04-11 20:40:20 +00:00
|
|
|
|
} else if ( layout.flippedY ) {
|
2018-04-23 21:23:17 +00:00
|
|
|
|
classes.push( 'flipped-y' );
|
2018-04-11 20:40:20 +00:00
|
|
|
|
} else if ( layout.flippedX ) {
|
2018-04-23 21:23:17 +00:00
|
|
|
|
classes.push( 'flipped-x' );
|
2017-02-14 20:17:32 +00:00
|
|
|
|
}
|
2016-11-21 12:07:23 +00:00
|
|
|
|
|
2018-04-13 17:29:57 +00:00
|
|
|
|
if ( ( !preview.hasThumbnail || preview.isTall && !layout.flippedX ) &&
|
|
|
|
|
!layout.flippedY ) {
|
2018-04-23 21:07:51 +00:00
|
|
|
|
classes.push( 'mwe-popups-no-image-pokey' );
|
2016-11-21 12:07:23 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-02-14 20:17:32 +00:00
|
|
|
|
if ( ( preview.hasThumbnail && !preview.isTall ) && !layout.flippedY ) {
|
2018-04-23 21:07:51 +00:00
|
|
|
|
classes.push( 'mwe-popups-image-pokey' );
|
2017-02-14 20:17:32 +00:00
|
|
|
|
}
|
2016-11-21 12:07:23 +00:00
|
|
|
|
|
2017-02-14 20:17:32 +00:00
|
|
|
|
if ( preview.isTall ) {
|
|
|
|
|
classes.push( 'mwe-popups-is-tall' );
|
|
|
|
|
} else {
|
|
|
|
|
classes.push( 'mwe-popups-is-not-tall' );
|
|
|
|
|
}
|
2016-11-21 12:07:23 +00:00
|
|
|
|
|
2017-02-14 20:17:32 +00:00
|
|
|
|
return classes;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Lays out the preview given the layout.
|
|
|
|
|
*
|
|
|
|
|
* If the thumbnail is landscape and isn't the full height of the thumbnail
|
|
|
|
|
* container, then pull the extract up to keep whitespace consistent across
|
|
|
|
|
* previews.
|
|
|
|
|
*
|
|
|
|
|
* @param {ext.popups.Preview} preview
|
|
|
|
|
* @param {ext.popups.PreviewLayout} layout
|
2017-05-05 19:36:04 +00:00
|
|
|
|
* @param {string[]} classes class names used for layout out the preview
|
|
|
|
|
* @param {number} predefinedLandscapeImageHeight landscape image height
|
|
|
|
|
* @param {number} pokeySize
|
2018-04-13 17:29:57 +00:00
|
|
|
|
* @return {void}
|
2017-02-14 20:17:32 +00:00
|
|
|
|
*/
|
2018-01-18 18:48:16 +00:00
|
|
|
|
export function layoutPreview(
|
|
|
|
|
preview, layout, classes, predefinedLandscapeImageHeight, pokeySize
|
|
|
|
|
) {
|
2018-03-19 19:39:41 +00:00
|
|
|
|
const popup = preview.el,
|
2017-02-14 20:17:32 +00:00
|
|
|
|
isTall = preview.isTall,
|
|
|
|
|
hasThumbnail = preview.hasThumbnail,
|
|
|
|
|
thumbnail = preview.thumbnail,
|
2018-04-13 17:29:57 +00:00
|
|
|
|
flippedY = layout.flippedY;
|
2018-03-19 19:39:41 +00:00
|
|
|
|
let offsetTop = layout.offset.top;
|
2017-02-14 20:17:32 +00:00
|
|
|
|
|
2017-05-05 19:36:04 +00:00
|
|
|
|
if (
|
|
|
|
|
!flippedY && !isTall && hasThumbnail &&
|
|
|
|
|
thumbnail.height < predefinedLandscapeImageHeight
|
|
|
|
|
) {
|
|
|
|
|
popup.find( '.mwe-popups-extract' ).css(
|
2017-02-14 20:17:32 +00:00
|
|
|
|
'margin-top',
|
2017-05-05 19:36:04 +00:00
|
|
|
|
thumbnail.height - pokeySize
|
2017-02-14 20:17:32 +00:00
|
|
|
|
);
|
|
|
|
|
}
|
2016-11-21 12:07:23 +00:00
|
|
|
|
|
2017-05-05 19:36:04 +00:00
|
|
|
|
popup.addClass( classes.join( ' ' ) );
|
2016-11-21 12:07:23 +00:00
|
|
|
|
|
2017-02-14 20:17:32 +00:00
|
|
|
|
if ( flippedY ) {
|
|
|
|
|
offsetTop -= popup.outerHeight();
|
|
|
|
|
}
|
2016-11-21 12:07:23 +00:00
|
|
|
|
|
2017-02-14 20:17:32 +00:00
|
|
|
|
popup.css( {
|
|
|
|
|
top: offsetTop,
|
2018-03-20 17:01:18 +00:00
|
|
|
|
left: `${ layout.offset.left }px`
|
2017-02-14 20:17:32 +00:00
|
|
|
|
} );
|
2016-11-21 12:07:23 +00:00
|
|
|
|
|
2018-04-13 17:29:57 +00:00
|
|
|
|
if ( hasThumbnail ) {
|
|
|
|
|
setThumbnailClipPath( preview, layout );
|
2016-11-21 12:07:23 +00:00
|
|
|
|
}
|
2018-04-13 17:29:57 +00:00
|
|
|
|
}
|
2016-11-21 12:07:23 +00:00
|
|
|
|
|
2018-04-13 17:29:57 +00:00
|
|
|
|
/**
|
|
|
|
|
* Sets the thumbnail SVG clip-path.
|
|
|
|
|
*
|
|
|
|
|
* If the preview should be oriented differently, then the pokey is updated,
|
|
|
|
|
* e.g. if the preview should be flipped vertically, then the pokey is
|
|
|
|
|
* removed.
|
|
|
|
|
*
|
|
|
|
|
* Note: SVG clip-paths are supported everywhere but clip-paths as CSS
|
|
|
|
|
* properties are not. https://caniuse.com/#feat=css-clip-path
|
|
|
|
|
*
|
|
|
|
|
* @param {ext.popups.Preview} preview
|
|
|
|
|
* @param {ext.popups.PreviewLayout} layout
|
|
|
|
|
* @return {void}
|
|
|
|
|
*/
|
|
|
|
|
function setThumbnailClipPath( { el, isTall }, { flippedY, flippedX } ) {
|
|
|
|
|
let mask;
|
|
|
|
|
if ( flippedX && !flippedY ) {
|
|
|
|
|
mask = isTall ? 'landscape-mask' : 'mask-flip';
|
|
|
|
|
} else if ( flippedY && flippedX && isTall ) {
|
|
|
|
|
mask = 'landscape-mask-flip';
|
|
|
|
|
} else if ( !flippedY && !isTall ) {
|
|
|
|
|
mask = 'mask';
|
2017-02-14 20:17:32 +00:00
|
|
|
|
}
|
2016-11-21 12:07:23 +00:00
|
|
|
|
|
2018-04-13 17:29:57 +00:00
|
|
|
|
if ( mask ) {
|
|
|
|
|
el.find( 'image' )[ 0 ]
|
|
|
|
|
.setAttribute( 'clip-path', `url(#mwe-popups-${mask})` );
|
2016-11-21 12:07:23 +00:00
|
|
|
|
}
|
2017-02-14 20:17:32 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Given the rectangular box(es) find the 'y' boundary of the closest
|
|
|
|
|
* rectangle to the point 'y'. The point 'y' is the location of the mouse
|
|
|
|
|
* on the 'y' axis and the rectangular box(es) are the borders of the
|
|
|
|
|
* element over which the mouse is located. There will be more than one
|
|
|
|
|
* rectangle in case the element spans multiple lines.
|
|
|
|
|
*
|
|
|
|
|
* In the majority of cases the mouse pointer will be inside a rectangle.
|
|
|
|
|
* However, some browsers (i.e. Chrome) trigger a hover action even when
|
|
|
|
|
* the mouse pointer is just outside a bounding rectangle. That's why
|
|
|
|
|
* we need to look at all rectangles and not just the rectangle that
|
|
|
|
|
* encloses the point.
|
|
|
|
|
*
|
2017-04-27 16:03:44 +00:00
|
|
|
|
* @private
|
2017-02-14 20:17:32 +00:00
|
|
|
|
* @param {Number} y the point for which the closest location is being
|
|
|
|
|
* looked for
|
|
|
|
|
* @param {ClientRectList} rects list of rectangles defined by four edges
|
|
|
|
|
* @param {Boolean} [isTop] should the resulting rectangle's top 'y'
|
|
|
|
|
* boundary be returned. By default the bottom 'y' value is returned.
|
|
|
|
|
* @return {Number}
|
|
|
|
|
*/
|
2017-07-28 17:32:46 +00:00
|
|
|
|
export function getClosestYPosition( y, rects, isTop ) {
|
2018-03-19 19:39:41 +00:00
|
|
|
|
let minY = null, result;
|
2017-02-14 20:17:32 +00:00
|
|
|
|
|
2018-03-20 18:03:13 +00:00
|
|
|
|
Array.prototype.slice.call( rects ).forEach( rect => {
|
2018-03-19 19:39:41 +00:00
|
|
|
|
const deltaY = Math.abs( y - rect.top + y - rect.bottom );
|
2017-02-14 20:17:32 +00:00
|
|
|
|
|
|
|
|
|
if ( minY === null || minY > deltaY ) {
|
|
|
|
|
minY = deltaY;
|
|
|
|
|
// Make sure the resulting point is at or outside the rectangle
|
|
|
|
|
// boundaries.
|
|
|
|
|
result = ( isTop ) ? Math.floor( rect.top ) : Math.ceil( rect.bottom );
|
|
|
|
|
}
|
|
|
|
|
} );
|
2016-11-21 12:07:23 +00:00
|
|
|
|
|
2017-02-14 20:17:32 +00:00
|
|
|
|
return result;
|
|
|
|
|
}
|