From 619aabf11628fc43ccfb6735b7851301a5dc267f Mon Sep 17 00:00:00 2001 From: Yukai Huang Date: Fri, 27 Mar 2020 22:27:57 +0800 Subject: [PATCH 1/7] Implement simple lightbox Signed-off-by: Yukai Huang --- public/js/extra.js | 1 + public/js/lib/renderer/lightbox/index.js | 101 +++++++++++++++++++ public/js/lib/renderer/lightbox/lightbox.css | 64 ++++++++++++ 3 files changed, 166 insertions(+) create mode 100644 public/js/lib/renderer/lightbox/index.js create mode 100644 public/js/lib/renderer/lightbox/lightbox.css diff --git a/public/js/extra.js b/public/js/extra.js index 337dd97d..7a9d5739 100644 --- a/public/js/extra.js +++ b/public/js/extra.js @@ -21,6 +21,7 @@ import { deserializeParamAttributeFromElement } from './lib/markdown/utils' import { renderFretBoard } from './lib/renderer/fretboard/fretboard' +import './lib/renderer/lightbox' import markdownit from 'markdown-it' import markdownitContainer from 'markdown-it-container' diff --git a/public/js/lib/renderer/lightbox/index.js b/public/js/lib/renderer/lightbox/index.js new file mode 100644 index 00000000..7d1b5ea0 --- /dev/null +++ b/public/js/lib/renderer/lightbox/index.js @@ -0,0 +1,101 @@ +import './lightbox.css' + +let images = [] +let currentImage = null +let currentIndexIndex = 0 + +function findOrCreateLightboxContainer () { + const lightboxContainerSelector = '.lightbox-container' + + let lightBoxContainer = document.querySelector(lightboxContainerSelector) + if (!lightBoxContainer) { + lightBoxContainer = document.createElement('div') + lightBoxContainer.className = 'lightbox-container' + + lightBoxContainer.innerHTML = ` + + + + + + ` + + const hideContainer = (e) => { + e.stopPropagation() + lightBoxContainer.classList.remove('show') + } + + lightBoxContainer.querySelector('.lightbox-control-previous').addEventListener('click', (e) => { + e.stopPropagation() + switchImage(-1) + }) + lightBoxContainer.querySelector('.lightbox-control-next').addEventListener('click', (e) => { + e.stopPropagation() + switchImage(1) + }) + lightBoxContainer.querySelector('.lightbox-control-close').addEventListener('click', hideContainer) + lightBoxContainer.addEventListener('click', hideContainer) + + document.body.appendChild(lightBoxContainer) + } + + return lightBoxContainer +} + +function switchImage (dir) { + const lightBoxContainer = findOrCreateLightboxContainer() + + currentIndexIndex += dir + if (currentIndexIndex >= images.length) { + currentIndexIndex = 0 + } else if (currentIndexIndex < 0) { + currentIndexIndex = images.length - 1 + } + + const img = images[currentIndexIndex] + + setImageInner(img, lightBoxContainer) +} + +function setImageInner (img, lightBoxContainer) { + const src = img.getAttribute('src') + const alt = img.getAttribute('alt') + + lightBoxContainer.querySelector('.lightbox-inner').innerHTML = `${alt}` +} + +function onClickImage (img) { + const lightBoxContainer = findOrCreateLightboxContainer() + + setImageInner(img, lightBoxContainer) + + lightBoxContainer.classList.add('show') + + currentImage = img + updateLightboxImages() +} + +function updateLightboxImages () { + images = [...document.querySelectorAll('.markdown-body img:not(.emoji)')] + + if (currentImage) { + currentIndexIndex = images.findIndex(image => image === currentImage) + } +} + +const init = () => { + const markdownBody = document.querySelector('.markdown-body') + if (!markdownBody) { + return + } + + markdownBody.addEventListener('click', function (e) { + if (e.target.nodeName === 'IMG' && !e.target.classList.contains('emoji')) { + onClickImage(e.target) + e.stopPropagation() + } + }) +} + +init() diff --git a/public/js/lib/renderer/lightbox/lightbox.css b/public/js/lib/renderer/lightbox/lightbox.css new file mode 100644 index 00000000..10e70466 --- /dev/null +++ b/public/js/lib/renderer/lightbox/lightbox.css @@ -0,0 +1,64 @@ +.lightbox-container.show { + display: flex !important; +} + +.lightbox-container { + display: none; + + position: fixed; + z-index: 99999; + background-color: rgba(255, 255, 255, 0.8); + top: 0; + left: 0; + width: 100%; + height: 100%; + + justify-content: center; + align-items: center; + padding: 0px 40px; +} + +.lightbox-container .lightbox-control-previous, +.lightbox-container .lightbox-control-next, +.lightbox-container .lightbox-control-close { + position: absolute; + width: 40px; + height: 40px; + color: rgba(65, 65, 65, 0.8); + text-align: center; + cursor: pointer; + user-select: none; + font-size: 25px; +} + +.lightbox-container .lightbox-control-previous:hover, +.lightbox-container .lightbox-control-next:hover, +.lightbox-container .lightbox-control-close:hover { + cursor: rgba(130, 130, 130, 0.78); +} + +.lightbox-container .lightbox-control-next, +.lightbox-container .lightbox-control-previous { + top: calc(50% - 10px); +} + +.lightbox-container .lightbox-control-previous { + left: 0; +} + +.lightbox-container .lightbox-control-next { + right: 0; +} + +.lightbox-container .lightbox-control-close { + top: 10px; + right: 10px; +} + +.lightbox-container .lightbox-inner img { + max-width: 100%; +} + +.markdown-body img:not(.emoji) { + cursor: zoom-in; +} From 62bbf928b77d47662fffb39dadbcf43f1d89256a Mon Sep 17 00:00:00 2001 From: Yukai Huang Date: Sun, 19 Apr 2020 20:59:35 +0800 Subject: [PATCH 2/7] Apply image lightbox only in document image Signed-off-by: Yukai Huang --- public/js/extra.js | 1 + public/js/lib/renderer/lightbox/index.js | 4 ++-- public/js/lib/renderer/lightbox/lightbox.css | 2 +- public/js/lib/syncscroll.js | 5 +++++ 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/public/js/extra.js b/public/js/extra.js index 7a9d5739..bf6c58c0 100644 --- a/public/js/extra.js +++ b/public/js/extra.js @@ -1167,6 +1167,7 @@ md.use(markdownitContainer, 'spoiler', { const defaultImageRender = md.renderer.rules.image md.renderer.rules.image = function (tokens, idx, options, env, self) { tokens[idx].attrJoin('class', 'raw') + tokens[idx].attrJoin('class', 'md-image') return defaultImageRender(...arguments) } md.renderer.rules.list_item_open = function (tokens, idx, options, env, self) { diff --git a/public/js/lib/renderer/lightbox/index.js b/public/js/lib/renderer/lightbox/index.js index 7d1b5ea0..3945b3a3 100644 --- a/public/js/lib/renderer/lightbox/index.js +++ b/public/js/lib/renderer/lightbox/index.js @@ -77,7 +77,7 @@ function onClickImage (img) { } function updateLightboxImages () { - images = [...document.querySelectorAll('.markdown-body img:not(.emoji)')] + images = [...document.querySelectorAll('.markdown-body img.md-image')] if (currentImage) { currentIndexIndex = images.findIndex(image => image === currentImage) @@ -91,7 +91,7 @@ const init = () => { } markdownBody.addEventListener('click', function (e) { - if (e.target.nodeName === 'IMG' && !e.target.classList.contains('emoji')) { + if (e.target.nodeName === 'IMG' && e.target.classList.contains('md-image')) { onClickImage(e.target) e.stopPropagation() } diff --git a/public/js/lib/renderer/lightbox/lightbox.css b/public/js/lib/renderer/lightbox/lightbox.css index 10e70466..a5e238cb 100644 --- a/public/js/lib/renderer/lightbox/lightbox.css +++ b/public/js/lib/renderer/lightbox/lightbox.css @@ -59,6 +59,6 @@ max-width: 100%; } -.markdown-body img:not(.emoji) { +.markdown-body img.md-image { cursor: zoom-in; } diff --git a/public/js/lib/syncscroll.js b/public/js/lib/syncscroll.js index 9daf327c..df771c8e 100644 --- a/public/js/lib/syncscroll.js +++ b/public/js/lib/syncscroll.js @@ -27,6 +27,11 @@ md.renderer.rules.table_open = function (tokens, idx, options, env, self) { addPart(tokens, idx) return self.renderToken(...arguments) } +const defaultImageRender = md.renderer.rules.image +md.renderer.rules.image = function (tokens, idx, options, env, self) { + tokens[idx].attrJoin('class', 'md-image') + return defaultImageRender(...arguments) +} md.renderer.rules.bullet_list_open = function (tokens, idx, options, env, self) { addPart(tokens, idx) return self.renderToken(...arguments) From 421998565886857827f70b15b932a9444224bd83 Mon Sep 17 00:00:00 2001 From: Yukai Huang Date: Sun, 19 Apr 2020 20:59:49 +0800 Subject: [PATCH 3/7] Fix body scrolling in published page Signed-off-by: Yukai Huang --- public/js/lib/renderer/lightbox/index.js | 2 ++ public/js/lib/renderer/lightbox/lightbox.css | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/public/js/lib/renderer/lightbox/index.js b/public/js/lib/renderer/lightbox/index.js index 3945b3a3..55c78042 100644 --- a/public/js/lib/renderer/lightbox/index.js +++ b/public/js/lib/renderer/lightbox/index.js @@ -24,6 +24,7 @@ function findOrCreateLightboxContainer () { const hideContainer = (e) => { e.stopPropagation() lightBoxContainer.classList.remove('show') + document.body.classList.remove('no-scroll') } lightBoxContainer.querySelector('.lightbox-control-previous').addEventListener('click', (e) => { @@ -71,6 +72,7 @@ function onClickImage (img) { setImageInner(img, lightBoxContainer) lightBoxContainer.classList.add('show') + document.body.classList.add('no-scroll') currentImage = img updateLightboxImages() diff --git a/public/js/lib/renderer/lightbox/lightbox.css b/public/js/lib/renderer/lightbox/lightbox.css index a5e238cb..14f29d01 100644 --- a/public/js/lib/renderer/lightbox/lightbox.css +++ b/public/js/lib/renderer/lightbox/lightbox.css @@ -62,3 +62,7 @@ .markdown-body img.md-image { cursor: zoom-in; } + +body.no-scroll { + overflow: hidden; +} From ec01735424bc379eea607780e478ac7132cdd689 Mon Sep 17 00:00:00 2001 From: Yukai Huang Date: Sun, 19 Apr 2020 23:31:27 +0800 Subject: [PATCH 4/7] Zoom/drag image Signed-off-by: Yukai Huang --- public/js/lib/renderer/lightbox/index.js | 78 +++++++++++++++++++- public/js/lib/renderer/lightbox/lightbox.css | 10 ++- 2 files changed, 83 insertions(+), 5 deletions(-) diff --git a/public/js/lib/renderer/lightbox/index.js b/public/js/lib/renderer/lightbox/index.js index 55c78042..ab263eaf 100644 --- a/public/js/lib/renderer/lightbox/index.js +++ b/public/js/lib/renderer/lightbox/index.js @@ -1,6 +1,7 @@ import './lightbox.css' let images = [] +/** @type {HTMLImageElement} */ let currentImage = null let currentIndexIndex = 0 @@ -21,10 +22,13 @@ function findOrCreateLightboxContainer () { ` + addImageZoomListener(lightBoxContainer) + const hideContainer = (e) => { e.stopPropagation() lightBoxContainer.classList.remove('show') document.body.classList.remove('no-scroll') + currentImage = null } lightBoxContainer.querySelector('.lightbox-control-previous').addEventListener('click', (e) => { @@ -36,7 +40,7 @@ function findOrCreateLightboxContainer () { switchImage(1) }) lightBoxContainer.querySelector('.lightbox-control-close').addEventListener('click', hideContainer) - lightBoxContainer.addEventListener('click', hideContainer) + // lightBoxContainer.addEventListener('click', hideContainer) document.body.appendChild(lightBoxContainer) } @@ -63,7 +67,8 @@ function setImageInner (img, lightBoxContainer) { const src = img.getAttribute('src') const alt = img.getAttribute('alt') - lightBoxContainer.querySelector('.lightbox-inner').innerHTML = `${alt}` + lightBoxContainer.querySelector('.lightbox-inner').innerHTML = `${alt}` + addImageDragListener(lightBoxContainer.querySelector('.lightbox-inner img')) } function onClickImage (img) { @@ -86,6 +91,70 @@ function updateLightboxImages () { } } +function addImageZoomListener (container) { + container.addEventListener('wheel', function (e) { + // normalize scroll position as percentage + e.preventDefault() + + /** @type {HTMLImageElement} */ + const image = container.querySelector('img') + + if (!image) { + return + } + + let scale = image.getBoundingClientRect().width / image.offsetWidth + scale += e.deltaY * -0.01 + + // Restrict scale + scale = Math.min(Math.max(0.125, scale), 4) + + var transformValue = `scale(${scale})` + + image.style.WebkitTransform = transformValue + image.style.MozTransform = transformValue + image.style.OTransform = transformValue + image.style.transform = transformValue + }) +} + +/** + * @param {HTMLImageElement} image + */ +function addImageDragListener (image) { + let moved = false + let pos = [] + image.addEventListener('mousedown', (evt) => { + moved = true + + const { left, top } = image.getBoundingClientRect() + + pos = [ + evt.pageX - left, + evt.pageY - top + ] + }, true) + + image.addEventListener('mousemove', (evt) => { + if (!moved) { + return + } + + image.style.left = `${evt.pageX - pos[0]}px` + image.style.top = `${evt.pageY - pos[1]}px` + image.style.position = 'absolute' + }, true) + + image.addEventListener('mouseup', () => { + moved = false + pos = [] + }, true) + + image.addEventListener('mouseout', () => { + moved = false + }) +} + const init = () => { const markdownBody = document.querySelector('.markdown-body') if (!markdownBody) { @@ -93,8 +162,9 @@ const init = () => { } markdownBody.addEventListener('click', function (e) { - if (e.target.nodeName === 'IMG' && e.target.classList.contains('md-image')) { - onClickImage(e.target) + const img = e.target + if (img.nodeName === 'IMG' && img.classList.contains('md-image')) { + onClickImage(img) e.stopPropagation() } }) diff --git a/public/js/lib/renderer/lightbox/lightbox.css b/public/js/lib/renderer/lightbox/lightbox.css index 14f29d01..bc5bca5b 100644 --- a/public/js/lib/renderer/lightbox/lightbox.css +++ b/public/js/lib/renderer/lightbox/lightbox.css @@ -29,12 +29,13 @@ cursor: pointer; user-select: none; font-size: 25px; + z-index: 1; } .lightbox-container .lightbox-control-previous:hover, .lightbox-container .lightbox-control-next:hover, .lightbox-container .lightbox-control-close:hover { - cursor: rgba(130, 130, 130, 0.78); + color: rgba(130, 130, 130, 0.78); } .lightbox-container .lightbox-control-next, @@ -57,6 +58,13 @@ .lightbox-container .lightbox-inner img { max-width: 100%; + cursor: move; + + transform-origin: 0 0; + -moz-transform-origin: 0 0; + -webkit-transform-origin: 0 0; + -ms-transform-origin: 0 0; + -o-transform-origin: 0 0; } .markdown-body img.md-image { From c22ce8da604088abd06356d2153741084c648853 Mon Sep 17 00:00:00 2001 From: Yukai Huang Date: Sun, 19 Apr 2020 23:38:31 +0800 Subject: [PATCH 5/7] Dark mode Signed-off-by: Yukai Huang --- public/js/lib/renderer/lightbox/index.js | 6 +++++- public/js/lib/renderer/lightbox/lightbox.css | 16 ++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/public/js/lib/renderer/lightbox/index.js b/public/js/lib/renderer/lightbox/index.js index ab263eaf..6ecf9253 100644 --- a/public/js/lib/renderer/lightbox/index.js +++ b/public/js/lib/renderer/lightbox/index.js @@ -40,7 +40,7 @@ function findOrCreateLightboxContainer () { switchImage(1) }) lightBoxContainer.querySelector('.lightbox-control-close').addEventListener('click', hideContainer) - // lightBoxContainer.addEventListener('click', hideContainer) + lightBoxContainer.addEventListener('click', hideContainer) document.body.appendChild(lightBoxContainer) } @@ -153,6 +153,10 @@ function addImageDragListener (image) { image.addEventListener('mouseout', () => { moved = false }) + + image.addEventListener('click', (e) => { + e.stopPropagation() + }) } const init = () => { diff --git a/public/js/lib/renderer/lightbox/lightbox.css b/public/js/lib/renderer/lightbox/lightbox.css index bc5bca5b..c8fe1438 100644 --- a/public/js/lib/renderer/lightbox/lightbox.css +++ b/public/js/lib/renderer/lightbox/lightbox.css @@ -18,6 +18,10 @@ padding: 0px 40px; } +.night .lightbox-container { + background-color: rgba(47, 47, 47, 0.8);; +} + .lightbox-container .lightbox-control-previous, .lightbox-container .lightbox-control-next, .lightbox-container .lightbox-control-close { @@ -32,12 +36,24 @@ z-index: 1; } +.night .lightbox-container .lightbox-control-previous, +.night .lightbox-container .lightbox-control-next, +.night .lightbox-container .lightbox-control-close { + color: rgba(255, 255, 255, 0.5); +} + .lightbox-container .lightbox-control-previous:hover, .lightbox-container .lightbox-control-next:hover, .lightbox-container .lightbox-control-close:hover { color: rgba(130, 130, 130, 0.78); } +.night .lightbox-container .lightbox-control-previous:hover, +.night .lightbox-container .lightbox-control-next:hover, +.night .lightbox-container .lightbox-control-close:hover { + color: rgba(255, 255, 255, 0.8); +} + .lightbox-container .lightbox-control-next, .lightbox-container .lightbox-control-previous { top: calc(50% - 10px); From 82253f496f0acc6b3a3f6aed78a6011ba6058a8c Mon Sep 17 00:00:00 2001 From: Yukai Huang Date: Tue, 21 Apr 2020 00:08:56 +0800 Subject: [PATCH 6/7] Support keyboard navigation Signed-off-by: Yukai Huang --- public/js/lib/renderer/lightbox/index.js | 34 +++++++++++++++++++++--- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/public/js/lib/renderer/lightbox/index.js b/public/js/lib/renderer/lightbox/index.js index 6ecf9253..84636e0f 100644 --- a/public/js/lib/renderer/lightbox/index.js +++ b/public/js/lib/renderer/lightbox/index.js @@ -5,6 +5,8 @@ let images = [] let currentImage = null let currentIndexIndex = 0 +let hideContainer + function findOrCreateLightboxContainer () { const lightboxContainerSelector = '.lightbox-container' @@ -24,8 +26,7 @@ function findOrCreateLightboxContainer () { addImageZoomListener(lightBoxContainer) - const hideContainer = (e) => { - e.stopPropagation() + hideContainer = () => { lightBoxContainer.classList.remove('show') document.body.classList.remove('no-scroll') currentImage = null @@ -39,8 +40,14 @@ function findOrCreateLightboxContainer () { e.stopPropagation() switchImage(1) }) - lightBoxContainer.querySelector('.lightbox-control-close').addEventListener('click', hideContainer) - lightBoxContainer.addEventListener('click', hideContainer) + lightBoxContainer.querySelector('.lightbox-control-close').addEventListener('click', (e) => { + e.stopPropagation() + hideContainer() + }) + lightBoxContainer.addEventListener('click', (e) => { + e.stopPropagation() + hideContainer() + }) document.body.appendChild(lightBoxContainer) } @@ -172,6 +179,25 @@ const init = () => { e.stopPropagation() } }) + + window.addEventListener('keydown', function (e) { + if (!currentImage) { + return + } + + if (e.key === 'ArrowRight') { + switchImage(1) + e.stopPropagation() + } else if (e.key === 'ArrowLeft') { + switchImage(-1) + e.stopPropagation() + } else if (e.key === 'Escape') { + if (hideContainer) { + hideContainer() + e.stopPropagation() + } + } + }) } init() From e3a6669c7e9ecb4cc2fc59f03c38f981bf7c5f5d Mon Sep 17 00:00:00 2001 From: Yukai Huang Date: Tue, 21 Apr 2020 00:26:03 +0800 Subject: [PATCH 7/7] Larger draggable area Signed-off-by: Yukai Huang --- public/js/lib/renderer/lightbox/index.js | 27 +++++++++++++------- public/js/lib/renderer/lightbox/lightbox.css | 23 +++++++++++++++++ 2 files changed, 41 insertions(+), 9 deletions(-) diff --git a/public/js/lib/renderer/lightbox/index.js b/public/js/lib/renderer/lightbox/index.js index 84636e0f..3619e0ae 100644 --- a/public/js/lib/renderer/lightbox/index.js +++ b/public/js/lib/renderer/lightbox/index.js @@ -131,7 +131,11 @@ function addImageZoomListener (container) { function addImageDragListener (image) { let moved = false let pos = [] - image.addEventListener('mousedown', (evt) => { + + const container = findOrCreateLightboxContainer() + const inner = container.querySelector('.lightbox-inner') + + const onMouseDown = (evt) => { moved = true const { left, top } = image.getBoundingClientRect() @@ -140,9 +144,11 @@ function addImageDragListener (image) { evt.pageX - left, evt.pageY - top ] - }, true) + } + image.addEventListener('mousedown', onMouseDown) + inner.addEventListener('mousedown', onMouseDown) - image.addEventListener('mousemove', (evt) => { + const onMouseMove = (evt) => { if (!moved) { return } @@ -150,17 +156,20 @@ function addImageDragListener (image) { image.style.left = `${evt.pageX - pos[0]}px` image.style.top = `${evt.pageY - pos[1]}px` image.style.position = 'absolute' - }, true) + } + image.addEventListener('mousemove', onMouseMove) + inner.addEventListener('mousemove', onMouseMove) - image.addEventListener('mouseup', () => { + const onMouseUp = () => { moved = false pos = [] - }, true) + } + image.addEventListener('mouseup', onMouseUp) + inner.addEventListener('mouseup', onMouseUp) - image.addEventListener('mouseout', () => { - moved = false + inner.addEventListener('click', (e) => { + e.stopPropagation() }) - image.addEventListener('click', (e) => { e.stopPropagation() }) diff --git a/public/js/lib/renderer/lightbox/lightbox.css b/public/js/lib/renderer/lightbox/lightbox.css index c8fe1438..54ff6948 100644 --- a/public/js/lib/renderer/lightbox/lightbox.css +++ b/public/js/lib/renderer/lightbox/lightbox.css @@ -72,6 +72,29 @@ right: 10px; } +.lightbox-container .lightbox-inner { + width: 100%; + height: 100%; + cursor: move; + + display: -webkit-box; + display: -webkit-flex; + display: -moz-box; + display: -ms-flexbox; + + display: flex; + -webkit-box-align: center; + -webkit-align-items: center; + -moz-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: center; + -webkit-justify-content: center; + -moz-box-pack: center; + -ms-flex-pack: center; + justify-content: center; +} + .lightbox-container .lightbox-inner img { max-width: 100%; cursor: move;