mirror of https://github.com/saimn/sigal.git
6 changed files with 509 additions and 25 deletions
@ -0,0 +1,29 @@
|
||||
{% macro img_description(media) -%} |
||||
{%- if media.big -%} |
||||
<a href='{{ media.big_url }}'>Full size</a> |
||||
{%- endif -%} |
||||
{%- if media.description -%} |
||||
<br>{{ media.description }} |
||||
{%- endif -%} |
||||
{%- if media.exif -%} |
||||
<div class='film-meta'> |
||||
{%- if media.exif.iso -%} |
||||
<abbr title='film speed'>{{ media.exif.iso }}</abbr> {%- endif -%} |
||||
{%- if media.exif.exposure -%} |
||||
<abbr title='exposure'>{{ media.exif.exposure }}</abbr> {%- endif -%} |
||||
{%- if media.exif.fstop -%} |
||||
<abbr title='aperture'>{{ media.exif.fstop }}</abbr> {%- endif -%} |
||||
{%- if media.exif.focal -%} |
||||
<abbr title='focal length'>{{ media.exif.focal }}</abbr> {%- endif -%} |
||||
</div> |
||||
{%- if media.exif.gps -%} |
||||
<a title='location' href='https://www.openstreetmap.org/?mlat={{ media.exif.gps.lat }}&mlon={{ media.exif.gps.lon}}&zoom=12&layers=M' target='_blank' class='map' >{{ 'N{:.6f}'.format(media.exif.gps.lat) if media.exif.gps.lat > 0 else 'S{:.6f}'.format(-media.exif.gps.lat) }}{{ 'E{:.6f}'.format(media.exif.gps.lon) if media.exif.gps.lon > 0 else 'W{:.6f}'.format(-media.exif.gps.lon) }}</a> |
||||
{%- endif -%} |
||||
{%- if media.exif.Make or media.exif.Model -%} |
||||
<abbr title='camera make and model'>{{ media.exif.Make }} {{ media.exif.Model }}</abbr> |
||||
{%- endif -%} |
||||
{%- if media.exif.datetime -%} |
||||
<abbr title='date'>{{ media.exif.datetime }}</abbr> |
||||
{%- endif -%} |
||||
{% endif %} |
||||
{%- endmacro %} |
||||
@ -0,0 +1,414 @@
|
||||
/** |
||||
* PhotoSwipe Dynamic Caption plugin v1.2.7 |
||||
* https://github.com/dimsemenov/photoswipe-dynamic-caption-plugin
|
||||
*
|
||||
* By https://dimsemenov.com
|
||||
*/ |
||||
|
||||
const defaultOptions = { |
||||
captionContent: '.pswp-caption-content', |
||||
type: 'auto', |
||||
horizontalEdgeThreshold: 20, |
||||
mobileCaptionOverlapRatio: 0.3, |
||||
mobileLayoutBreakpoint: 600, |
||||
verticallyCenterImage: false |
||||
}; |
||||
|
||||
class PhotoSwipeDynamicCaption { |
||||
constructor(lightbox, options) { |
||||
this.options = { |
||||
...defaultOptions, |
||||
...options |
||||
}; |
||||
|
||||
this.lightbox = lightbox; |
||||
|
||||
this.lightbox.on('init', () => { |
||||
this.pswp = this.lightbox.pswp; |
||||
this.initCaption(); |
||||
}); |
||||
} |
||||
|
||||
initCaption() { |
||||
const { pswp } = this; |
||||
|
||||
pswp.on('change', () => { |
||||
// make sure caption is displayed after slides are switched
|
||||
this.showCaption(this.pswp.currSlide); |
||||
}); |
||||
|
||||
pswp.on('calcSlideSize', (e) => this.onCalcSlideSize(e)); |
||||
|
||||
pswp.on('slideDestroy', (e) => { |
||||
if (e.slide.dynamicCaption) { |
||||
if (e.slide.dynamicCaption.element) { |
||||
e.slide.dynamicCaption.element.remove(); |
||||
} |
||||
delete e.slide.dynamicCaption; |
||||
} |
||||
}); |
||||
|
||||
// hide caption if zoomed
|
||||
pswp.on('zoomPanUpdate', ({ slide }) => { |
||||
if (pswp.opener.isOpen && slide.dynamicCaption) { |
||||
if (slide.currZoomLevel > slide.zoomLevels.initial) { |
||||
this.hideCaption(slide); |
||||
} else { |
||||
this.showCaption(slide); |
||||
} |
||||
|
||||
// move caption on vertical drag
|
||||
if (slide.dynamicCaption.element) { |
||||
let captionYOffset = 0; |
||||
if (slide.currZoomLevel <= slide.zoomLevels.initial) { |
||||
const shiftedAmount = slide.pan.y - slide.bounds.center.y; |
||||
if (Math.abs(shiftedAmount) > 1) { |
||||
captionYOffset = shiftedAmount; |
||||
} |
||||
} |
||||
|
||||
this.setCaptionYOffset(slide.dynamicCaption.element, captionYOffset); |
||||
} |
||||
|
||||
this.adjustPanArea(slide, slide.currZoomLevel); |
||||
} |
||||
}); |
||||
|
||||
pswp.on('beforeZoomTo', (e) => { |
||||
this.adjustPanArea(pswp.currSlide, e.destZoomLevel); |
||||
}); |
||||
|
||||
// Stop default action of tap when tapping on the caption
|
||||
pswp.on('tapAction', (e) => { |
||||
if (e.originalEvent.target.closest('.pswp__dynamic-caption')) { |
||||
e.preventDefault(); |
||||
} |
||||
}); |
||||
} |
||||
|
||||
adjustPanArea(slide, zoomLevel) { |
||||
if (slide.dynamicCaption && slide.dynamicCaption.adjustedPanAreaSize) { |
||||
if (zoomLevel > slide.zoomLevels.initial) { |
||||
slide.panAreaSize.x = slide.dynamicCaption.originalPanAreaSize.x; |
||||
slide.panAreaSize.y = slide.dynamicCaption.originalPanAreaSize.y; |
||||
} else { |
||||
// Restore panAreaSize after we zoom back to initial position
|
||||
slide.panAreaSize.x = slide.dynamicCaption.adjustedPanAreaSize.x; |
||||
slide.panAreaSize.y = slide.dynamicCaption.adjustedPanAreaSize.y; |
||||
} |
||||
} |
||||
} |
||||
|
||||
useMobileLayout() { |
||||
const { mobileLayoutBreakpoint } = this.options; |
||||
|
||||
if (typeof mobileLayoutBreakpoint === 'function') { |
||||
return mobileLayoutBreakpoint.call(this); |
||||
} else if (typeof mobileLayoutBreakpoint === 'number') { |
||||
if (window.innerWidth < mobileLayoutBreakpoint) { |
||||
return true; |
||||
} |
||||
} |
||||
|
||||
return false; |
||||
} |
||||
|
||||
hideCaption(slide) { |
||||
if (slide.dynamicCaption && !slide.dynamicCaption.hidden) { |
||||
const captionElement = slide.dynamicCaption.element; |
||||
|
||||
if (!captionElement) { |
||||
return; |
||||
} |
||||
|
||||
slide.dynamicCaption.hidden = true; |
||||
captionElement.classList.add('pswp__dynamic-caption--faded'); |
||||
|
||||
// Disable caption visibility with the delay, so it's not interactable
|
||||
if (slide.captionFadeTimeout) { |
||||
clearTimeout(slide.captionFadeTimeout); |
||||
} |
||||
slide.captionFadeTimeout = setTimeout(() => { |
||||
captionElement.style.visibility = 'hidden'; |
||||
delete slide.captionFadeTimeout; |
||||
}, 400); |
||||
} |
||||
} |
||||
|
||||
setCaptionYOffset(el, y) { |
||||
el.style.transform = `translateY(${y}px)`; |
||||
} |
||||
|
||||
showCaption(slide) { |
||||
if (slide.dynamicCaption && slide.dynamicCaption.hidden) { |
||||
const captionElement = slide.dynamicCaption.element; |
||||
|
||||
if (!captionElement) { |
||||
return; |
||||
} |
||||
|
||||
slide.dynamicCaption.hidden = false; |
||||
captionElement.style.visibility = 'visible'; |
||||
|
||||
clearTimeout(slide.captionFadeTimeout); |
||||
slide.captionFadeTimeout = setTimeout(() => { |
||||
captionElement.classList.remove('pswp__dynamic-caption--faded'); |
||||
delete slide.captionFadeTimeout;; |
||||
}, 50); |
||||
} |
||||
} |
||||
|
||||
setCaptionPosition(captionEl, x, y) { |
||||
const isOnHorizontalEdge = (x <= this.options.horizontalEdgeThreshold); |
||||
captionEl.classList[ |
||||
isOnHorizontalEdge ? 'add' : 'remove' |
||||
]('pswp__dynamic-caption--on-hor-edge'); |
||||
|
||||
captionEl.style.left = x + 'px'; |
||||
captionEl.style.top = y + 'px'; |
||||
} |
||||
|
||||
setCaptionWidth(captionEl, width) { |
||||
if (!width) { |
||||
captionEl.style.removeProperty('width'); |
||||
} else { |
||||
captionEl.style.width = width + 'px'; |
||||
} |
||||
} |
||||
|
||||
setCaptionType(captionEl, type) { |
||||
const prevType = captionEl.dataset.pswpCaptionType; |
||||
if (type !== prevType) { |
||||
captionEl.classList.add('pswp__dynamic-caption--' + type); |
||||
captionEl.classList.remove('pswp__dynamic-caption--' + prevType); |
||||
captionEl.dataset.pswpCaptionType = type; |
||||
} |
||||
} |
||||
|
||||
updateCaptionPosition(slide) { |
||||
if (!slide.dynamicCaption || !slide.dynamicCaption.type || !slide.dynamicCaption.element) { |
||||
return; |
||||
} |
||||
|
||||
if (slide.dynamicCaption.type === 'mobile') { |
||||
this.setCaptionType( |
||||
slide.dynamicCaption.element,
|
||||
slide.dynamicCaption.type |
||||
); |
||||
|
||||
slide.dynamicCaption.element.style.removeProperty('left'); |
||||
slide.dynamicCaption.element.style.removeProperty('top'); |
||||
this.setCaptionWidth(slide.dynamicCaption.element, false); |
||||
return; |
||||
} |
||||
|
||||
const zoomLevel = slide.zoomLevels.initial; |
||||
const imageWidth = Math.ceil(slide.width * zoomLevel); |
||||
const imageHeight = Math.ceil(slide.height * zoomLevel); |
||||
|
||||
this.setCaptionType(slide.dynamicCaption.element, slide.dynamicCaption.type); |
||||
if (slide.dynamicCaption.type === 'aside') { |
||||
this.setCaptionPosition( |
||||
slide.dynamicCaption.element, |
||||
slide.bounds.center.x + imageWidth, |
||||
slide.bounds.center.y |
||||
); |
||||
this.setCaptionWidth(slide.dynamicCaption.element, false); |
||||
} else if (slide.dynamicCaption.type === 'below') { |
||||
this.setCaptionPosition( |
||||
slide.dynamicCaption.element, |
||||
slide.bounds.center.x, |
||||
slide.bounds.center.y + imageHeight |
||||
); |
||||
this.setCaptionWidth(slide.dynamicCaption.element, imageWidth); |
||||
} |
||||
} |
||||
|
||||
onCalcSlideSize(e) { |
||||
const { slide } = e; |
||||
let captionSize; |
||||
let useMobileVersion; |
||||
|
||||
if (!slide.dynamicCaption) { |
||||
slide.dynamicCaption = { |
||||
element: undefined, |
||||
type: false, |
||||
hidden: false |
||||
}; |
||||
|
||||
const captionHTML = this.getCaptionHTML(slide); |
||||
|
||||
if (!captionHTML) { |
||||
return; |
||||
} |
||||
|
||||
slide.dynamicCaption.element = document.createElement('div'); |
||||
slide.dynamicCaption.element.className = 'pswp__dynamic-caption pswp__hide-on-close'; |
||||
slide.dynamicCaption.element.innerHTML = captionHTML; |
||||
|
||||
this.pswp.dispatch('dynamicCaptionUpdateHTML', {
|
||||
captionElement: slide.dynamicCaption.element, |
||||
slide |
||||
}); |
||||
|
||||
slide.holderElement.appendChild(slide.dynamicCaption.element); |
||||
} |
||||
|
||||
if (!slide.dynamicCaption.element) { |
||||
return; |
||||
} |
||||
|
||||
this.storeOriginalPanAreaSize(slide); |
||||
|
||||
slide.bounds.update(slide.zoomLevels.initial); |
||||
|
||||
if (this.useMobileLayout()) { |
||||
slide.dynamicCaption.type = 'mobile'; |
||||
useMobileVersion = true; |
||||
} else { |
||||
if (this.options.type === 'auto') { |
||||
if (slide.bounds.center.x > slide.bounds.center.y) { |
||||
slide.dynamicCaption.type = 'aside'; |
||||
} else { |
||||
slide.dynamicCaption.type = 'below'; |
||||
} |
||||
} else { |
||||
slide.dynamicCaption.type = this.options.type; |
||||
} |
||||
}
|
||||
|
||||
const imageWidth = Math.ceil(slide.width * slide.zoomLevels.initial); |
||||
const imageHeight = Math.ceil(slide.height * slide.zoomLevels.initial); |
||||
|
||||
this.setCaptionType( |
||||
slide.dynamicCaption.element,
|
||||
slide.dynamicCaption.type |
||||
); |
||||
|
||||
if (slide.dynamicCaption.type === 'aside') { |
||||
this.setCaptionWidth(slide.dynamicCaption.element, false); |
||||
captionSize = this.measureCaptionSize(slide.dynamicCaption.element, e.slide); |
||||
|
||||
const captionWidth = captionSize.x;
|
||||
|
||||
const horizontalEnding = imageWidth + slide.bounds.center.x; |
||||
const horizontalLeftover = (slide.panAreaSize.x - horizontalEnding); |
||||
|
||||
if (horizontalLeftover <= captionWidth) { |
||||
slide.panAreaSize.x -= captionWidth; |
||||
this.recalculateZoomLevelAndBounds(slide); |
||||
} else { |
||||
// do nothing, caption will fit aside without any adjustments
|
||||
} |
||||
} else if (slide.dynamicCaption.type === 'below' || useMobileVersion) { |
||||
this.setCaptionWidth( |
||||
slide.dynamicCaption.element,
|
||||
useMobileVersion ? this.pswp.viewportSize.x : imageWidth |
||||
); |
||||
|
||||
captionSize = this.measureCaptionSize(slide.dynamicCaption.element, e.slide); |
||||
const captionHeight = captionSize.y; |
||||
|
||||
if (this.options.verticallyCenterImage) { |
||||
slide.panAreaSize.y -= captionHeight; |
||||
this.recalculateZoomLevelAndBounds(slide); |
||||
} else { |
||||
// Lift up the image only by caption height
|
||||
|
||||
// vertical ending of the image
|
||||
const verticalEnding = imageHeight + slide.bounds.center.y; |
||||
|
||||
// height between bottom of the screen and ending of the image
|
||||
// (before any adjustments applied)
|
||||
const verticalLeftover = slide.panAreaSize.y - verticalEnding; |
||||
const initialPanAreaHeight = slide.panAreaSize.y; |
||||
|
||||
if (verticalLeftover <= captionHeight) { |
||||
// lift up the image to give more space for caption
|
||||
slide.panAreaSize.y -= Math.min((captionHeight - verticalLeftover) * 2, captionHeight); |
||||
|
||||
// we reduce viewport size, thus we need to update zoom level and pan bounds
|
||||
this.recalculateZoomLevelAndBounds(slide); |
||||
|
||||
const maxPositionX = slide.panAreaSize.x * this.options.mobileCaptionOverlapRatio / 2; |
||||
|
||||
// Do not reduce viewport height if too few space available
|
||||
if (useMobileVersion
|
||||
&& slide.bounds.center.x > maxPositionX) { |
||||
// Restore the default position
|
||||
slide.panAreaSize.y = initialPanAreaHeight; |
||||
this.recalculateZoomLevelAndBounds(slide); |
||||
} |
||||
} |
||||
} |
||||
} else { |
||||
// mobile
|
||||
} |
||||
|
||||
this.storeAdjustedPanAreaSize(slide); |
||||
this.updateCaptionPosition(slide); |
||||
} |
||||
|
||||
measureCaptionSize(captionEl, slide) { |
||||
const rect = captionEl.getBoundingClientRect(); |
||||
const event = this.pswp.dispatch('dynamicCaptionMeasureSize', { |
||||
captionEl, |
||||
slide, |
||||
captionSize: { |
||||
x: rect.width, |
||||
y: rect.height |
||||
} |
||||
}); |
||||
return event.captionSize; |
||||
} |
||||
|
||||
recalculateZoomLevelAndBounds(slide) { |
||||
slide.zoomLevels.update(slide.width, slide.height, slide.panAreaSize); |
||||
slide.bounds.update(slide.zoomLevels.initial); |
||||
} |
||||
|
||||
storeAdjustedPanAreaSize(slide) { |
||||
if (slide.dynamicCaption) { |
||||
if (!slide.dynamicCaption.adjustedPanAreaSize) { |
||||
slide.dynamicCaption.adjustedPanAreaSize = {}; |
||||
} |
||||
slide.dynamicCaption.adjustedPanAreaSize.x = slide.panAreaSize.x; |
||||
slide.dynamicCaption.adjustedPanAreaSize.y = slide.panAreaSize.y; |
||||
} |
||||
} |
||||
|
||||
storeOriginalPanAreaSize(slide) { |
||||
if (slide.dynamicCaption) { |
||||
if (!slide.dynamicCaption.originalPanAreaSize) { |
||||
slide.dynamicCaption.originalPanAreaSize = {}; |
||||
} |
||||
slide.dynamicCaption.originalPanAreaSize.x = slide.panAreaSize.x; |
||||
slide.dynamicCaption.originalPanAreaSize.y = slide.panAreaSize.y; |
||||
} |
||||
} |
||||
|
||||
getCaptionHTML(slide) { |
||||
if (typeof this.options.captionContent === 'function') { |
||||
return this.options.captionContent.call(this, slide); |
||||
} |
||||
|
||||
const currSlideElement = slide.data.element; |
||||
let captionHTML = ''; |
||||
if (currSlideElement) { |
||||
const hiddenCaption = currSlideElement.querySelector(this.options.captionContent); |
||||
if (hiddenCaption) { |
||||
// get caption from element with class pswp-caption-content
|
||||
captionHTML = hiddenCaption.innerHTML; |
||||
} else { |
||||
const img = currSlideElement.querySelector('img'); |
||||
if (img) { |
||||
// get caption from alt attribute
|
||||
captionHTML = img.getAttribute('alt'); |
||||
} |
||||
} |
||||
} |
||||
return captionHTML; |
||||
} |
||||
} |
||||
|
||||
export default PhotoSwipeDynamicCaption; |
||||
File diff suppressed because one or more lines are too long
Loading…
Reference in new issue