diff --git a/README.rst b/README.rst index 14c2644..0f223fd 100644 --- a/README.rst +++ b/README.rst @@ -1,7 +1,8 @@ sphinx_galleria ############### -Create image galleries with Sphinx. +Create image galleries with Sphinx. No external JS +dependency. Install ~~~~~~~ @@ -12,6 +13,11 @@ If you're using a virtualenv, just: pip install sphinx-galleria +.. important:: + + Your webserver must deliver .mjs file with correct + content type (`application/javascript`). + Using ~~~~~ @@ -23,24 +29,30 @@ Just use the galleria directive like this: .. code:: rst .. galleria:: imagefile1 imagefile2 images*.jpg - :galleria: (optional) Name of gallery - :description: (optional) A long description - :title: (optional) Title of image - :thumbsize: (optional) Image size (defaults to "100x100") - :transition: (optional, once) Transition for gallery - :theme: (optional, once) Theme name for galleria - :class: (optional, once) HTML class for gallery + :galleria: (optional) Name of gallery - node level + :alt: (optional) A long description - image level + :title: (optional) Title of image - image level + :thumbsize: (optional) Image size (defaults to "100x100") - image level + :width: Width of image (in pixel or percentage or with unit, default auto) - node level + :height: (optional) Height of image (in pixel or with unit) - node level + :align: Align to 'right', 'left' or 'center' (default to center) - node level + :hide_title: Flag - hide title under image (not in dialog) - image level + :hide_alt: Flag - hide alternative text under image - image level + :transition: (optional, once) Transition for gallery - image level + :class: (optional, once) HTML class for gallery - node level -Title, thumbnail size and description are same for all -images defined by the directive. If you need separated -descriptions and titles, just use the directive several -times with the same galleria name. In this case, theme -html class and transition must be defined only in -one directive. +Image level options are same for all images defined by +the directive. If you need separated descriptions and +titles, just use the directive several times with the +same galleria name. In this case, node level options +must be defined only once. Thumbnail size is in "WIDTHxHEIGHT" format, resulting image keeps ratio. +The first image of a gallery is displayed if javascript +is not available on the browser. + Licensing ~~~~~~~~~ diff --git a/setup.py b/setup.py index 296d206..d53f634 100644 --- a/setup.py +++ b/setup.py @@ -17,7 +17,7 @@ setup( package_data={ "sphinx_galleria": [ "*.py", - "static/sphinxgalleria/*.js", + "static/sphinxgalleria/*.mjs", "static/sphinxgalleria/*.css", ] }, diff --git a/sphinx_galleria/__init__.py b/sphinx_galleria/__init__.py index 15d91b6..68b378d 100644 --- a/sphinx_galleria/__init__.py +++ b/sphinx_galleria/__init__.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- """ -Create image galleries with GalleriaJS +Create image galleries """ import os from typing import Dict, Any @@ -39,8 +39,28 @@ def copy_images_files(app: Sphinx, env: BuildEnvironment) -> None: ensuredir(os.path.dirname(dest)) with Image.open(original) as im: - im.thumbnail(thumbsize) - im.save(dest, "JPEG") + if im.size[0]/im.size[1] > thumbsize[0]/thumbsize[1]: + out = im.resize(( + thumbsize[0], + thumbsize[1] + ), box=( + (im.size[0]-thumbsize[1]*im.size[0]/thumbsize[0])//2, + 0, + (im.size[0]+thumbsize[1]*im.size[0]/thumbsize[0])//2, + im.size[1] + )) + else: + out = im.resize(( + thumbsize[0], + thumbsize[1] + ), box=( + 0, + (im.size[1]-thumbsize[0]*im.size[1]/thumbsize[0])//2, + im.size[0], + (im.size[1]+thumbsize[0]*im.size[1]/thumbsize[0])//2, + )) + + out.save(dest, "JPEG") def install_static_files(app: Sphinx, env: BuildEnvironment) -> None: @@ -69,12 +89,16 @@ def install_static_files(app: Sphinx, env: BuildEnvironment) -> None: copyfile(source_path, static_path) - if static.endswith('.js'): - app.add_js_file(os.path.join('sphinxgalleria', static)) - elif static.endswith('.css'): + if static.endswith('.css'): app.add_css_file(os.path.join('sphinxgalleria', static)) +def init_directive(app: Sphinx, config) -> None: + if 'galleria_override_image' in config and \ + config['galleria_override_image']: + app.add_directive('image', directive.GalleriaDirective) + + def setup(app: Sphinx) -> Dict[str, Any]: app.add_node( directive.galleria, @@ -88,6 +112,8 @@ def setup(app: Sphinx) -> Dict[str, Any]: ) app.add_directive('galleria', directive.GalleriaDirective) app.add_env_collector(collector.GalleriaCollector) + app.add_config_value('galleria_override_image', False, 'boolean') app.connect('env-updated', install_static_files) app.connect('env-updated', copy_images_files) + app.connect('config-inited', init_directive) return {'version': __version__} diff --git a/sphinx_galleria/collector.py b/sphinx_galleria/collector.py index e253e3c..01d551f 100644 --- a/sphinx_galleria/collector.py +++ b/sphinx_galleria/collector.py @@ -84,7 +84,7 @@ class GalleriaCollector(EnvironmentCollector): images.append({ 'title': imageglob['title'], - 'description': imageglob['description'], + 'alt': imageglob['alt'], 'thumb': os.path.relpath( thumb_path_jpg, app.env.srcdir), diff --git a/sphinx_galleria/directive.py b/sphinx_galleria/directive.py index 0ce7011..77f0158 100644 --- a/sphinx_galleria/directive.py +++ b/sphinx_galleria/directive.py @@ -17,6 +17,8 @@ from sphinx.writers.latex import LaTeXTranslator from sphinx.writers.texinfo import TexinfoTranslator from sphinx.writers.text import TextTranslator from sphinx.writers.manpage import ManualPageTranslator +from sphinx.util.osutil import relative_uri +from sphinx.locale import __ class galleria(nodes.General, nodes.Element): @@ -27,15 +29,30 @@ def html_visit_galleria(self: HTMLTranslator, node: galleria) -> None: galleria_id = 'galleria-%s' % uuid.uuid4() self.body.append( - "
" % ( + "
" % ( galleria_id, - node['class'] + node['class'] + ' align-%s' % node['options']['align'], + node['options']['width'], + node['options']['height'], ) ) + if len(node['images']) > 0: + self.body.append("
") + self.body.append("%s" % ( + node['images'][0]['image'], + node['images'][0]['title'], + node['images'][0]['alt'] + )) + self.body.append('
') + self.body.append( - "" % ( + "
" % ( galleria_id, json.dumps(node['options']), json.dumps(node['images']) @@ -47,66 +64,84 @@ def html_visit_galleria(self: HTMLTranslator, node: galleria) -> None: def latex_visit_galleria(self: LaTeXTranslator, node: galleria) -> None: for image in node['images']: - self.body.append('[%s]' % image['description']) + self.body.append('[%s]' % image['alt']) raise nodes.SkipNode def texinfo_visit_galleria(self: TexinfoTranslator, node: galleria) -> None: for image in node['images']: - self.body.append('[%s]' % image['description']) + self.body.append('[%s]' % image['alt']) raise nodes.SkipNode def text_visit_galleria(self: TextTranslator, node: galleria) -> None: for image in node['images']: - self.body.append('[%s]' % image['description']) + self.body.append('[%s]' % image['alt']) raise nodes.SkipNode def gemini_visit_galleria(self, node: galleria) -> None: for image in node['images']: - self.body.append('=> %s %s' % (image['path'], image['description'])) + self.body.append('=> %s %s' % (image['path'], image['alt'])) raise nodes.SkipNode def man_visit_galleria(self: ManualPageTranslator, node: galleria) -> None: - if 'description' in node.attributes: - self.body.append('[%s]' % node['description']) + if 'alt' in node.attributes: + self.body.append('[%s]' % node['alt']) raise nodes.SkipNode +def align_choices(argument): + return directives.choice(argument, ('left', 'right', 'center')) + + class GalleriaDirective(Directive): has_content = False required_arguments = 1 final_argument_whitespace = True + option_spec = { - "class": directives.unchanged, + "class": directives.class_option, "galleria": directives.unchanged, - "description": directives.unchanged, + "alt": directives.unchanged, "title": directives.unchanged, "thumbsize": directives.unchanged, "transition": directives.unchanged, + 'width': directives.length_or_percentage_or_unitless, + 'height': directives.length_or_unitless, + 'align': align_choices, + 'hide_title': directives.flag, + 'hide_alt': directives.flag, } def run(self): - if not self.state.document.hasattr('galleria_nodes'): - self.state.document.galleria_nodes = {} + source = self.state.document.settings._source + document = self.state.document + if not document.hasattr('galleria_nodes'): + document.galleria_nodes = {} + if source not in document.galleria_nodes: + document.galleria_nodes[source] = {} - galleria_name = self.options.get('galleria') + galleria_name = self.options.get('galleria') or uuid.uuid4() created = False - if galleria_name and \ - galleria_name in self.state.document.galleria_nodes: - node = self.state.document.galleria_nodes[galleria_name] + if galleria_name in document.galleria_nodes[source]: + node = document.galleria_nodes[source][galleria_name] else: node = galleria() node['class'] = 'galleria' node['options'] = { - 'transition': 'fade' + 'transition': 'fade', + 'label_prev': __('Previous'), + 'label_next': __('Next'), + 'label_close': __('Close'), + 'label_thumbnail': __('Thumbnail, click to enlarge'), } node['images'] = [] + document.galleria_nodes[source][galleria_name] = node created = True @@ -115,12 +150,18 @@ class GalleriaDirective(Directive): if self.options.get('transition'): node['options']['transition'] = self.options['transition'] + node['options']["width"] = self.options.get('width') or 'auto' + node['options']["height"] = self.options.get('height') or 'auto' + node['options']["align"] = self.options.get('align') or 'center' + images_path = self.arguments for path in images_path: image = {} - image["description"] = self.options.get('description') + image["alt"] = self.options.get('alt') image["title"] = self.options.get('title') image["thumbsize"] = self.options.get('thumbsize') or '100x100' + image["hide_alt"] = bool(self.options.get('hide_alt')) + image["hide_title"] = bool(self.options.get('hide_title')) image["image"] = path node['images'].append(image) diff --git a/sphinx_galleria/static/sphinxgalleria/sphinxgalleria.css b/sphinx_galleria/static/sphinxgalleria/sphinxgalleria.css index e69de29..ce8d94c 100644 --- a/sphinx_galleria/static/sphinxgalleria/sphinxgalleria.css +++ b/sphinx_galleria/static/sphinxgalleria/sphinxgalleria.css @@ -0,0 +1,283 @@ +.sphinxgalleria-core { + text-align: center; +} + +.sphinxgalleria-core.align-center { + margin: auto; +} + +.sphinxgalleria-core.align-left { + float: left; +} + +.sphinxgalleria-core.align-right { + float: right; +} + +.sphinxgalleria-core figure .row { + flex-flow: row nowrap; + display: flex; +} + +.sphinxgalleria-core figure .row button { + flex-grow: 1; + flex-shrink: 1; + cursor: pointer; +} + +.sphinxgalleria-core figure .row button.button-modal { + flex-grow: 1000; + flex-shrink: 1000; + margin: 0; + padding: 0; + border: none; + width: max-content; + height: max-content; + display: flex; + flex-flow: column nowrap; +} + +.sphinxgalleria-core figure .row button.button-modal .caption { + background-color: hsl(0, 0%, 34%); + color: white; + text-transform: none; + border-bottom-left-radius: .5rem; + border-bottom-right-radius: .5rem; + border: none; + margin: 0; + padding: .5rem 1.5rem .5rem 1.5rem; + width: 100%; + text-align: left; +} + +.sphinxgalleria-core figure .row button.button-modal figcaption { + font-style: italic; + text-align: center; +} + +.sphinxgalleria-core figure .row button.button-modal img { + margin: 0; + padding: 0; + border: none; + width: auto; + height: auto; + object-fit: contain; +} + +.sphinxgalleria-core figure input[type=range] { + width: 100%; + height: 1.5rem; + border: none; + -webkit-appearance: none; +} +.sphinxgalleria-core figure input[type=range]:focus { + outline: none; + border: none; +} +.sphinxgalleria-core figure input[type=range]::-webkit-slider-runnable-track { + width: 100%; + height: .1rem; + padding: 0 1rem 0 1rem; + margin: 0; + border: none; + cursor: pointer; + animate: 0.2s; + background-color: hsl(0, 0%, 34%); +} +.sphinxgalleria-core figure input[type=range]::-webkit-slider-thumb { + height: 1rem; + width: 1rem; + border-radius: .5rem; + border: none; + background-color: hsl(0, 0%, 34%); + cursor: pointer; + -webkit-appearance: none; + margin-top: -.5rem; +} +.sphinxgalleria-core figure input[type=range]:focus::-webkit-slider-runnable-track { + background-color: hsl(0, 0%, 34%); + border: none; +} +.sphinxgalleria-core figure input[type=range]::-moz-range-track { + width: 100%; + padding: 0 1rem 0 1rem; + margin: 0; + border: none; + height: .1rem; + cursor: pointer; + animate: 0.2s; + background-color: hsl(0, 0%, 34%); +} +.sphinxgalleria-core figure input[type=range]::-moz-range-thumb { + height: 1rem; + width: 1rem; + border-radius: .5rem; + background-color: hsl(0, 0%, 34%); + cursor: pointer; +} + +.sphinxgalleria-core .mask { + position: fixed; + top: 0; + left: 0; + height: 100vh; + width: 100vw; + margin: 0; + padding: 0; + background-color: hsla(0, 0%, 0%, 80%); + z-index: 1000; +} + +.sphinxgalleria-core .submask { + height: 100%; + width: 100%; + margin: 0; + padding: 0; + position: relative; +} + +.sphinxgalleria-core dialog { + position: absolute; + top: 3vh; + left: 0; + height: 90vh; + max-height: 90vh; + width: max-content; + margin: 0 auto 0 auto; + padding-left: 3rem; + padding-right: 3rem; + background-color: black; + color: white; + border: white solid 0.2rem; + border-radius: 0.5rem; + text-align: center; + display: flex; + flex-flow: column nowrap; +} + +.sphinxgalleria-core dialog img { + height: 100%; + width: auto; + object-fit: contain; + flex-grow: 1000; + flex-shrink: 1000; + margin: 0.5rem 0 0.5rem 0; + padding: 0; +} + +.sphinxgalleria-core dialog menu { + text-align: left; + width: 100%; + padding: 0; + margin: 0; +} + +.sphinxgalleria-core dialog button { + border: none; + padding: .5em; + background-color: black; + color: white; +} + +.sphinxgalleria-core dialog button:hover, +.sphinxgalleria-core dialog button:focus { + background-color: hsl(0, 0%, 34%); + transition: background-color ease 0.4s; +} + +.sphinxgalleria-core dialog header button { + position: absolute; + top: .5rem; + right: -.5rem; +} + +.sphinxgalleria-core dialog header button::after { + content: '✕'; +} + +.sphinxgalleria-core dialog header { + position: relative; + min-height: 3rem; +} + +.sphinxgalleria-core dialog menu { + height: 3rem; +} + +.sphinxgalleria-core dialog header::after { + clear: both; +} + +.sphinxgalleria-core dialog menu button.close { + float: right; +} +.sphinxgalleria-core dialog menu button.close::before { + margin-right: 0.5rem; + content: '✕'; +} + +.sphinxgalleria-core figure button.prev, +.sphinxgalleria-core figure button.next { + background-color: hsl(0, 0%, 34%); + color: white; + height: 4rem; + width: 4rem; + margin: auto .5rem auto .5rem; + border-radius: 1rem; + border: none; +} + +.sphinxgalleria-core figure button.prev:hover, +.sphinxgalleria-core figure button.next:hover, +.sphinxgalleria-core figure button.prev:focus, +.sphinxgalleria-core figure button.next:focus { + background-color: hsl(0, 0%, 64%); + transition: background-color ease .4s; +} + +.sphinxgalleria-core figure button.prev { +} + +.sphinxgalleria-core figure button.next { +} + +.sphinxgalleria-core figure button.prev::before, +.sphinxgalleria-core dialog menu button.prev::before { + margin-right: 0.5rem; + content: '←'; +} + +.sphinxgalleria-core figure button.next::after, +.sphinxgalleria-core dialog menu button.next::after { + margin-left: 0.5rem; + content: '→'; +} + +.sphinxgalleria-core ul.thumbnails { + overflow-x: hidden; + margin: 0 auto 0 auto; + padding: 0; + max-width: 100%; + height: max-content; + display: inline-flex; + flex-flow: row nowrap; + justify-content: flex-start; +} + +.sphinxgalleria-core ul.thumbnails li { + list-style: none; +} + +.sphinxgalleria-core ul.thumbnails li input[type=radio] { + display: none; +} + +.sphinxgalleria-core ul.thumbnails li input:checked + label { + opacity: .5; +} + +.sphinxgalleria-core ul.thumbnails li img { + margin: 0.5rem; + cursor: pointer; + max-width: unset; +} diff --git a/sphinx_galleria/static/sphinxgalleria/sphinxgalleria.js b/sphinx_galleria/static/sphinxgalleria/sphinxgalleria.js deleted file mode 100644 index adc3f0e..0000000 --- a/sphinx_galleria/static/sphinxgalleria/sphinxgalleria.js +++ /dev/null @@ -1,59 +0,0 @@ -"use strict"; - -class SphinxGalleria { - constructor(target, options, data) { - var self = this; - self.target = target; - self.options = options; - self.data = data; - - self.node_target = null; - self.node_figure = document.createElement('figure'); - self.node_image = document.createElement('img'); - self.node_caption = document.createElement('figcaption'); - self.node_thumbnails = document.createElement('div'); - self.node_dialog = document.createElement('dialog', modal=true); - - var button_prev = document.createElement('button', id=self.target+"-prev") - var button_next = document.createElement('button', id=self.target+"-next") - button_prev.appendChild(document.createTextNode('<')); - button_next.appendChild(document.createTextNode('>')); - - button_prev.onkeypress = self.onprev - button_prev.onclick = self.onprev - - button_next.onkeypress = self.onnext - button_.onclick = self.onnext - - self.node_figure.appendChild(self.node_image); - self.node_figure.appendChild(button_prev); - self.node_figure.appendChild(button_next); - self.node_figure.appendChild(self.node_caption); - } - - init() { - var self = this; - self.node_target = document.getElementById(self.target); - self.node_target.innerHTML = ''; - self.node_target.appendChild(self.node_figure); - self.node_target.appendChild(self.node_thumbnails); - self.node_target.appendChild(self.node_dialog); - } - - onprev(event) { - var key = event.keyCode || event.charCode || event.which; - if(event.type==='keypress' && key!==13 && key!==32) { - return - } - } - - onnext(event) { - var key = event.keyCode || event.charCode || event.which; - if(event.type==='keypress' && key!==13 && key!==32) { - return - } - } - - changeImage() { - } -} diff --git a/sphinx_galleria/static/sphinxgalleria/sphinxgalleria.mjs b/sphinx_galleria/static/sphinxgalleria/sphinxgalleria.mjs new file mode 100644 index 0000000..a80ebd7 --- /dev/null +++ b/sphinx_galleria/static/sphinxgalleria/sphinxgalleria.mjs @@ -0,0 +1,368 @@ +"use strict()"; + +export class SphinxGalleria { + constructor(target, options, data) { + var self = this; + self.target = target; + self.options = options; + self.data = data; + self.oneimage = self.data.length === 1; + + self.node_target = null; + self.node_figure = document.createElement('figure'); + self.button_modal = document.createElement('button'); + self.button_modal.setAttribute('aria-haspopup', 'dialog'); + self.button_modal.setAttribute('class', 'button-modal'); + + self.node_image = document.createElement('img'); + self.node_caption = document.createElement('figcaption'); + self.node_alt = document.createElement('p'); + self.node_alt.setAttribute('id', self.target+'-'+'alt'); + self.node_image.setAttribute('aria-describedby', self.target+'-'+'alt'); + self.node_thumbnails = document.createElement('ul'); + self.node_thumbnails.setAttribute('class', 'thumbnails'); + self.node_dialog = document.createElement('dialog'); + self.node_mask = document.createElement('div'); + self.node_mask.setAttribute('class', 'mask'); + self.node_mask.hidden = true; + var node_submask = document.createElement('div'); + node_submask.setAttribute('class', 'submask'); + + self.node_div_caption = document.createElement('div'); + self.node_div_caption.setAttribute('class', 'caption'); + + var figure_row = document.createElement('div'); + figure_row.setAttribute('class', 'row'); + + self.dialog_button_close = document.createElement('button'); + self.dialog_button_close.setAttribute('class', 'close'); + + self.dialog_button_close_icon = document.createElement('button'); + self.dialog_button_close_icon.setAttribute('aria-label', self.options.label_close); + + self.dialog_image = document.createElement('img'); + self.dialog_image.setAttribute('aria-describedby', self.target+'-'+'alt'); + + self.dialog_title = document.createElement('h1'); + var dialog_header = document.createElement('header'); + var dialog_menu = document.createElement('menu'); + + if(self.oneimage) { + self.node_thumbnails.hidden = true; + self.node_thumbnails.setAttribute('aria-hidden', true); + self.node_thumbnails.style.visibility = 'hidden'; + } else { + self.node_slider = document.createElement('input'); + self.node_slider.setAttribute('type', 'range'); + self.node_slider.setAttribute('min', 1); + self.node_slider.setAttribute('max', self.data.length); + self.node_slider.setAttribute('value', 1); + + self.button_prev = document.createElement('button'); + self.button_prev.setAttribute('class', 'prev'); + self.button_prev.setAttribute('aria-label', self.options.label_prev); + + self.button_next = document.createElement('button'); + self.button_next.setAttribute('class', 'next'); + self.button_next.setAttribute('aria-label', self.options.label_next); + + self.dialog_button_prev = document.createElement('button'); + self.dialog_button_prev.setAttribute('class', 'prev'); + self.dialog_button_next = document.createElement('button'); + self.dialog_button_next.setAttribute('class', 'next'); + self.dialog_button_prev.appendChild(document.createTextNode(self.options.label_prev)); + self.dialog_button_next.appendChild(document.createTextNode(self.options.label_next)); + + figure_row.appendChild(self.button_prev); + dialog_menu.appendChild(self.dialog_button_prev); + dialog_menu.appendChild(self.dialog_button_next); + } + + node_submask.appendChild(self.node_dialog); + self.node_mask.appendChild(node_submask); + + figure_row.appendChild(self.button_modal); + self.node_figure.appendChild(figure_row); + + self.node_div_caption.appendChild(self.node_caption); + self.node_div_caption.appendChild(self.node_alt); + self.button_modal.appendChild(self.node_image); + self.button_modal.appendChild(self.node_div_caption); + + self.dialog_button_close.appendChild(document.createTextNode(self.options.label_close)); + + dialog_header.appendChild(self.dialog_title); + dialog_header.appendChild(self.dialog_button_close_icon); + self.node_dialog.appendChild(dialog_header); + self.node_dialog.appendChild(self.dialog_image); + dialog_menu.appendChild(self.dialog_button_close); + self.node_dialog.appendChild(dialog_menu); + + if(!self.oneimage) { + figure_row.appendChild(self.button_next); + self.node_figure.appendChild(self.node_slider); + } + } + + init() { + var self = this; + self.node_target = document.getElementById(self.target); + self.node_target.innerHTML = ''; + self.node_target.setAttribute( + 'class', + self.node_target.getAttribute('class') + " sphinxgalleria-core" + ); + self.node_target.appendChild(self.node_figure); + self.node_target.appendChild(self.node_thumbnails); + self.node_target.appendChild(self.node_mask); + + var onmodal = function(event) { + var key = event.keyCode || event.charCode || event.which; + if(event.type==='keypress' && key!==13 && key!==32) { + return; + } + event.preventDefault(); + self.showModal(); + }; + self.button_modal.addEventListener('click', onmodal); + self.button_modal.addEventListener('keypress', onmodal); + + var onclose = function(event) { + var key = event.keyCode || event.charCode || event.which; + if(event.type==='keypress' && key!==13 && key!==32) { + return; + } + event.preventDefault(); + self.closeModal(); + }; + + self.node_mask.addEventListener('click', onclose); + self.node_dialog.addEventListener('click', function(e) {e.stopPropagation();}); + self.dialog_button_close.addEventListener('click', onclose); + self.dialog_button_close.addEventListener('keypress', onclose); + self.dialog_button_close_icon.addEventListener('click', onclose); + self.dialog_button_close_icon.addEventListener('keypress', onclose); + + if(!self.oneimage) { + var onprev = function(event) { + var key = event.keyCode || event.charCode || event.which; + if(event.type==='keypress' && key!==13 && key!==32) { + return; + } + event.preventDefault(); + self.prev(); + }; + + var onnext = function(event) { + var key = event.keyCode || event.charCode || event.which; + if(event.type==='keypress' && key!==13 && key!==32) { + return; + } + event.preventDefault(); + self.next(); + }; + + self.button_prev.addEventListener('keypress', onprev); + self.button_prev.addEventListener('click', onprev); + + self.button_next.addEventListener('keypress', onnext); + self.button_next.addEventListener('click', onnext); + + self.dialog_button_prev.addEventListener('keypress', onprev); + self.dialog_button_prev.addEventListener('click', onprev); + + self.dialog_button_next.addEventListener('keypress', onnext); + self.dialog_button_next.addEventListener('click', onnext); + + self.dialog_image.addEventListener('click', function(e) { + var x = e.layerX - e.target.x; + if(x < e.target.width/2) { + self.prev(); + e.preventDefault(); + } else if(x > e.target.width/2) { + self.next(); + e.preventDefault(); + } + }); + + self.node_slider.addEventListener('change', function() { + var idx = self.node_slider.value - 1; + self.node_thumbnails.querySelector('input[data-index="'+idx+'"]').click(); + }); + } + + self.data.forEach(function(image_data, idx) { + var image_element = document.createElement('li'); + var image_button = document.createElement('input'); + image_button.setAttribute('type', 'radio'); + image_button.setAttribute('value', image_data.image); + image_button.setAttribute('name', self.target); + image_button.setAttribute('data-index', idx); + image_button.setAttribute('id', self.target+'-'+idx); + if(idx===0) { + image_button.checked = true; + } + + image_element.appendChild(image_button); + + var image_label = document.createElement('label'); + image_label.setAttribute('for', self.target+'-'+idx); + image_element.appendChild(image_label); + + var image_thumb = document.createElement('img'); + image_thumb.setAttribute('src', image_data.thumb); + + if(image_data.alt) { + image_button.setAttribute('data-alt', image_data.alt); + image_thumb.setAttribute('alt', image_data.alt); + } else { + image_thumb.setAttribute('alt', self.options.label_thumbnail); + } + + if(image_data.title) { + image_button.setAttribute('data-title', image_data.title); + } + if(image_data.hide_alt) { + image_button.setAttribute('data-hide-alt', true); + } + if(image_data.hide_alt) { + image_button.setAttribute('data-hide-title', true); + } + + if(image_data.transition) { + image_thumb.setAttribute('data-transition', image_data.transition); + } + + image_label.appendChild(image_thumb); + self.node_thumbnails.appendChild(image_element); + + image_button.addEventListener('change', function() {self.changeImage();}); + }); + + self.node_target.addEventListener('keypress', function(event) { + var key = event.keyCode || event.charCode || event.which; + + if(!self.oneimage && key===37) { + self.prev(); + } else if(!self.oneimage && key===39) { + self.next(); + } else if(key===27) { + self.closeModal(); + } + }); + + self.changeImage(); + } + + prev() { + var self = this; + if(!self.oneimage) { + var idx = self.node_thumbnails.querySelector('input:checked').getAttribute('data-index'); + idx = parseInt(idx) - 1; + if(idx < 0) { + idx = self.data.length - 1; + } + document.getElementById(self.target+'-'+idx).checked = true; + self.changeImage(); + } + } + + next() { + var self = this; + if(!self.oneimage) { + var idx = self.node_thumbnails.querySelector('input:checked').getAttribute('data-index'); + idx = parseInt(idx) + 1; + if(idx > self.data.length - 1) { + idx = 0; + } + document.getElementById(self.target+'-'+idx).checked = true; + self.changeImage(); + } + } + + changeImage() { + var self = this; + var current = self.node_thumbnails.querySelector('input:checked'); + var title = current.getAttribute('data-title'); + var alt = current.getAttribute('data-alt'); + var hide_title = current.hasAttribute('data-hide-title'); + var hide_alt = current.hasAttribute('data-hide-alt'); + var url = current.getAttribute('value'); + var hide_caption = true; + var idx = parseInt(current.getAttribute('data-index')); + + if(!self.oneimage) { + if(self.node_slider.value !== idx+1) { + self.node_slider.value = idx + 1; + } + var current_img = current.nextSibling.childNodes[0]; + self.node_thumbnails.scrollTo({ + left: current_img.x - + self.node_thumbnails.offsetWidth/2 + + current_img.clientWidth/2, + top: 0, + behavior: 'smooth' + }); + } + + if(!hide_title && title) { + self.node_caption.innerHTML = title; + self.node_caption.hidden = false; + self.dialog_title.innerHTML = title; + self.dialog_title.hidden = false; + hide_caption = false; + } else if(title) { + self.node_caption.innerHTML = ''; + self.node_caption.hidden = true; + self.dialog_title.innerHTML = title; + self.dialog_title.hidden = false; + } else { + self.node_caption.innerHTML = ''; + self.node_caption.hidden = true; + self.dialog_title.innerHTML = ''; + self.dialog_title.hidden = true; + } + + if(!hide_alt && alt) { + self.node_alt.innerHTML = alt; + self.node_alt.hidden = false; + hide_caption = false; + } else { + self.node_alt.innerHTML = ''; + self.node_alt.hidden = true; + } + + if(hide_caption) { + self.node_div_caption.hidden = true; + } else { + self.node_div_caption.hidden = false; + } + + self.node_image.setAttribute('src', url); + self.dialog_image.setAttribute('src', url); + } + + showModal() { + this.node_mask.hidden = false; + if(this.node_dialog.showModal) { + this.node_dialog.showModal(); + } else { + this.node_dialog.open = true; + this.node_dialog.setAttribute('open', true); + this.node_dialog.setAttribute('aria-modal', true); + this.node_dialog.hidden = false; + } + } + + closeModal() { + if(this.node_dialog.showModal) { + this.node_dialog.close(); + } else { + this.node_dialog.open = false; + this.node_dialog.removeAttribute('open'); + this.node_dialog.removeAttribute('aria-modal'); + this.node_dialog.hidden = true; + } + this.node_mask.hidden = true; + } +}