From fd8587c7c64e129d3630a3982a17458374561a1f Mon Sep 17 00:00:00 2001 From: Rodrigo Silva Mendoza Date: Tue, 27 Jul 2021 12:06:44 -0700 Subject: [PATCH 01/12] inital working modal --- layouts/categories/default.ejs | 4 +++ layouts/partials/footer.ejs | 30 +++++++++++++++++++++++ styles/partials/core/_categories.scss | 5 ++++ styles/partials/core/_furniture.scss | 35 +++++++++++++++++++++++++++ 4 files changed, 74 insertions(+) diff --git a/layouts/categories/default.ejs b/layouts/categories/default.ejs index c0a35393..43cbd1b3 100644 --- a/layouts/categories/default.ejs +++ b/layouts/categories/default.ejs @@ -26,6 +26,10 @@ <%- include('partials/childrenList', {children, kicker: template('folder.childrenList.kicker', title)}) %> <% } %> +
+ × + +
<%- include('partials/footer', { pageType: 'document', topLevelFolder: url.split('/')[1] }) %> diff --git a/layouts/partials/footer.ejs b/layouts/partials/footer.ejs index 1ccff568..4c51a234 100644 --- a/layouts/partials/footer.ejs +++ b/layouts/partials/footer.ejs @@ -97,4 +97,34 @@ }) // no callback on this. }) + window.onload = () => { + const imgs = document.querySelectorAll('.g-main-content img'); // get all images + imgs.forEach(img => { + img.addEventListener('click', (event) => { + expandImage(img.src); + }); + }); + } + + var imgModal = document.querySelector('.image-modal') + var modalImg = document.querySelector('.image-modal .modal-image'); + + + function expandImage(imgSrc) { + imgModal.style.display = 'block'; + modalImg.src = imgSrc; + } + + var modalCloseBtn = document.querySelector('.image-modal .close'); + modalCloseBtn.addEventListener('click', () => { + imgModal.style.display = 'none'; + }); + + /* keydown listener so img modal can be closed with Esc key */ + document.addEventListener('keydown', (event) => { + if (event.key === 'Escape' && imgModal.style.display === 'block') { + imgModal.style.display = 'none'; + } + }); + diff --git a/styles/partials/core/_categories.scss b/styles/partials/core/_categories.scss index 4c0fccd0..f97375b9 100644 --- a/styles/partials/core/_categories.scss +++ b/styles/partials/core/_categories.scss @@ -271,6 +271,11 @@ padding: 10px; display: block; margin: 20px auto; + cursor: pointer; + + &:hover { + opacity: 0.8; + } @include tablet{ max-width: 580px; diff --git a/styles/partials/core/_furniture.scss b/styles/partials/core/_furniture.scss index c657235d..1584d5c0 100644 --- a/styles/partials/core/_furniture.scss +++ b/styles/partials/core/_furniture.scss @@ -451,3 +451,38 @@ text-align: center; } } + +.image-modal { + display: none; + position: fixed; + z-index: 11; /* search bar icon has z-index of 10 */ + padding-top: 100px; + left: 0; + top: 0; + width: auto; + height: 100%; + overflow: hidden; + background-color: rgba(0,0,0,0.9); /* dim black allows images to pop */ +} + +.modal-image { + margin: auto; + display: block; + max-width: 100%; +} + +.image-modal .close { + position: absolute; + top: 50px; + right: 35px; + color: white; + font-size: 40px; + font-weight: bold; + transition: 0.3s; + + &:hover, &:focus { + color: #bbb; + text-decoration: none; + cursor: pointer; + } +} From 0c6658518e5c53a87411f0bdeee064b46f1f8b80 Mon Sep 17 00:00:00 2001 From: Rodrigo Silva Mendoza Date: Fri, 30 Jul 2021 16:51:21 -0700 Subject: [PATCH 02/12] full screen image modal, images with expand icon --- layouts/categories/default.ejs | 11 ++++- layouts/partials/footer.ejs | 8 +++- server/formatter.js | 10 ++++ styles/partials/core/_categories.scss | 6 +-- styles/partials/core/_furniture.scss | 68 ++++++++++++++++++++------- 5 files changed, 78 insertions(+), 25 deletions(-) diff --git a/layouts/categories/default.ejs b/layouts/categories/default.ejs index 43cbd1b3..a651d802 100644 --- a/layouts/categories/default.ejs +++ b/layouts/categories/default.ejs @@ -27,8 +27,15 @@ <% } %>
- × - + +
+ +
<%- include('partials/footer', { pageType: 'document', topLevelFolder: url.split('/')[1] }) %> diff --git a/layouts/partials/footer.ejs b/layouts/partials/footer.ejs index 4c51a234..a0b78cf0 100644 --- a/layouts/partials/footer.ejs +++ b/layouts/partials/footer.ejs @@ -113,7 +113,12 @@ function expandImage(imgSrc) { imgModal.style.display = 'block'; modalImg.src = imgSrc; - } + + window.addEventListener('scroll', function onScroll() { + window.removeEventListener('scroll', onScroll); + imgModal.style.display='none'; + }); + }; var modalCloseBtn = document.querySelector('.image-modal .close'); modalCloseBtn.addEventListener('click', () => { @@ -126,5 +131,4 @@ imgModal.style.display = 'none'; } }); - diff --git a/server/formatter.js b/server/formatter.js index 805901c1..3a2ad278 100644 --- a/server/formatter.js +++ b/server/formatter.js @@ -92,6 +92,16 @@ function normalizeHtml(html) { $(el).attr('href', libraryDeepLink || decoded) } + // Wrap images in a div, and add an expand button with an svg inside it + // svg could be stored in a server side images folder, then loaded with the + // dataLoader, but forces normalizeHtml and its calling func to be async + // console.time() reports about ~1ms for this entire block, both when + // declaring the button+svg string inline or as a variable outside this func + if (el.tagName === 'img') { + $(el).wrap('
'); + $(el).parent().append(''); + } + return el }) diff --git a/styles/partials/core/_categories.scss b/styles/partials/core/_categories.scss index f97375b9..7c0927e7 100644 --- a/styles/partials/core/_categories.scss +++ b/styles/partials/core/_categories.scss @@ -271,11 +271,7 @@ padding: 10px; display: block; margin: 20px auto; - cursor: pointer; - - &:hover { - opacity: 0.8; - } + cursor: pointer; // images can be opened in a modal @include tablet{ max-width: 580px; diff --git a/styles/partials/core/_furniture.scss b/styles/partials/core/_furniture.scss index 1584d5c0..6c734693 100644 --- a/styles/partials/core/_furniture.scss +++ b/styles/partials/core/_furniture.scss @@ -452,17 +452,40 @@ } } +// the image-wrapper class is added for the benefit of the +// Image Modal, so co-locating it here +.image-wrapper { + position: relative; + + .expand-image-btn { + height: 60px; + width: 60px; + position: absolute; + bottom: 25px; + right: 25px; + opacity: 0; + transition: opacity 0.3s ease 0s; + background-color: transparent; + pointer-events: none; + border: none; + } + + &:hover { + .expand-image-btn { + opacity: 1; + } + } +} + + .image-modal { display: none; position: fixed; - z-index: 11; /* search bar icon has z-index of 10 */ - padding-top: 100px; - left: 0; - top: 0; - width: auto; - height: 100%; + z-index: 1000000090; /* search bar icon has z-index of 1*10 */ + inset: 0px; /* shorthand for top,right,bottom,left at the same time */ overflow: hidden; - background-color: rgba(0,0,0,0.9); /* dim black allows images to pop */ + background-color: rgb(255, 255, 255); /* full white allows images to pop */ + transition: display 0.2s ease 0s; } .modal-image { @@ -472,17 +495,30 @@ } .image-modal .close { + display: flex; + align-items: center; position: absolute; - top: 50px; - right: 35px; - color: white; - font-size: 40px; - font-weight: bold; - transition: 0.3s; + top: 10px; + right: 10px; + background-color: transparent; + cursor: pointer; + border: 0.5px solid white; + border-radius: 50%; + width: 60px; + height: 60px; + transition: all 0.1s ease-in; + padding: 0px; &:hover, &:focus { - color: #bbb; - text-decoration: none; - cursor: pointer; + background-color: #f9f9f9; + border-color: lightgray; } } + +.image-modal .img-wrapper { + display: flex; + align-items: center; + align-content: center; + height: 100%; + padding: 30px; +} From 53ab83e57e26fb6a688b430eb38153ac701467c9 Mon Sep 17 00:00:00 2001 From: Rodrigo Silva Mendoza Date: Tue, 3 Aug 2021 15:49:32 -0700 Subject: [PATCH 03/12] add img processing test --- server/formatter.js | 8 ++++---- test/unit/htmlProcessing.test.js | 18 ++++++++++++++++++ 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/server/formatter.js b/server/formatter.js index 3a2ad278..27609617 100644 --- a/server/formatter.js +++ b/server/formatter.js @@ -94,12 +94,12 @@ function normalizeHtml(html) { // Wrap images in a div, and add an expand button with an svg inside it // svg could be stored in a server side images folder, then loaded with the - // dataLoader, but forces normalizeHtml and its calling func to be async - // console.time() reports about ~1ms for this entire block, both when + // dataLoader, but that forces normalizeHtml and its calling func to be async + // console.time() reports about ~1ms for this entire block, both when // declaring the button+svg string inline or as a variable outside this func if (el.tagName === 'img') { - $(el).wrap('
'); - $(el).parent().append(''); + $(el).wrap('
') + $(el).parent().append('') } return el diff --git a/test/unit/htmlProcessing.test.js b/test/unit/htmlProcessing.test.js index f7ac54ac..1d9fb8f6 100644 --- a/test/unit/htmlProcessing.test.js +++ b/test/unit/htmlProcessing.test.js @@ -65,6 +65,24 @@ describe('HTML processing', () => { const widthMatch = imageWidth.attr('style').match('width') assert.isNotNull(widthMatch) }) + + it('wraps img elements in a div, and appends button containing svg to same div', () => { + const numImages = (testGlobal.rawHTML.match(/ { + assert.equal($(elem).children().length, 2) + assert.isNotNull($(elem).find('.expand-image-btn')) + assert.isNotNull($(elem).find('img')) + }) + }); }) describe('list handling', () => { From 4411198ed478de6453f7ed77ab027dcae2f9558e Mon Sep 17 00:00:00 2001 From: Rodrigo Silva Mendoza Date: Thu, 5 Aug 2021 14:46:33 -0700 Subject: [PATCH 04/12] modify server svg loading, move client svg to import --- layouts/categories/default.ejs | 5 +---- public/images/minimizeIcon.svg | 1 + server/formatter.js | 14 +++++++++----- server/ssrComponents/expandIcon.svg | 1 + 4 files changed, 12 insertions(+), 9 deletions(-) create mode 100644 public/images/minimizeIcon.svg create mode 100644 server/ssrComponents/expandIcon.svg diff --git a/layouts/categories/default.ejs b/layouts/categories/default.ejs index a651d802..5d832d18 100644 --- a/layouts/categories/default.ejs +++ b/layouts/categories/default.ejs @@ -28,10 +28,7 @@
diff --git a/public/images/minimizeIcon.svg b/public/images/minimizeIcon.svg new file mode 100644 index 00000000..b3f70b79 --- /dev/null +++ b/public/images/minimizeIcon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/server/formatter.js b/server/formatter.js index 27609617..6fbdd3f9 100644 --- a/server/formatter.js +++ b/server/formatter.js @@ -3,6 +3,7 @@ const cheerio = require('cheerio') const qs = require('querystring') const unescape = require('unescape') const hljs = require('highlight.js') +const fs = require('fs') const list = require('./list') /* Your one stop shop for all your document processing needs. */ @@ -18,6 +19,12 @@ function normalizeHtml(html) { const $p = $('p') const isClean = $('meta[name="library-html-doc"]').attr('content') === '1' + let svgString + // load svg that will be used on images. takes ~0.2ms + if (html.indexOf('img') !== -1) { + svgString = fs.readFileSync('server/ssrComponents/expandIcon.svg', 'utf8') + } + // Remove p tags in Table of Contents $p.each((index, p) => { if (p.children.length < 1) return // don't search any empty p tags @@ -93,13 +100,10 @@ function normalizeHtml(html) { } // Wrap images in a div, and add an expand button with an svg inside it - // svg could be stored in a server side images folder, then loaded with the - // dataLoader, but that forces normalizeHtml and its calling func to be async - // console.time() reports about ~1ms for this entire block, both when - // declaring the button+svg string inline or as a variable outside this func + // to the wrapper if (el.tagName === 'img') { $(el).wrap('
') - $(el).parent().append('') + $(el).parent().append(``) } return el diff --git a/server/ssrComponents/expandIcon.svg b/server/ssrComponents/expandIcon.svg new file mode 100644 index 00000000..389cc5ed --- /dev/null +++ b/server/ssrComponents/expandIcon.svg @@ -0,0 +1 @@ + \ No newline at end of file From 216da17e41ffc3d2566f40f2676405b04dadfb0c Mon Sep 17 00:00:00 2001 From: Rodrigo Silva Mendoza Date: Thu, 5 Aug 2021 15:30:56 -0700 Subject: [PATCH 05/12] move imgModal adding to server --- custom/README.md | 4 ++++ layouts/categories/default.ejs | 9 --------- server/formatter.js | 16 +++++++++++++++ server/ssrComponents/imageModal.html | 8 ++++++++ .../ssrComponents}/minimizeIcon.svg | 0 styles/partials/core/_furniture.scss | 20 ++++++++++--------- 6 files changed, 39 insertions(+), 18 deletions(-) create mode 100644 server/ssrComponents/imageModal.html rename {public/images => server/ssrComponents}/minimizeIcon.svg (100%) diff --git a/custom/README.md b/custom/README.md index b51e3c92..963eb1e6 100644 --- a/custom/README.md +++ b/custom/README.md @@ -89,6 +89,10 @@ The site name, logo, and most of the text on the website can be modified from th placing a value for the same key in `custom/strings.yaml`, with a custom string, Javascript function, or image path. +## Image Modal +Images can opened/expanded in a modal/dialog. On hover of an image, the cursor will turn into the pointer style, and an expand button will show. Once clicked, the image will center in the page, and a minimize icon will show. Styles for the modal can be found in +`/styles/partials/core/_furniture.scss` as well as one line in `/styles/partials/core/_base.scss` that adds a pointer cursor on hover of images. The image modal is added server side, code for which can be found [here](../server/formatter.js#L168). `img` elements are wrapped in an overlay that allows for an expand button to show on hover during, code for which can be found [here](../server/formatter.js#L104). All components of the image modal that are used on the server can be found in the `/server/ssrComponents/` directory. Finally, the javascript that enables the image modal is in `/layouts/partials/footer.ejs`. + ## Middleware Middleware can be added to the beginning or end of the request cycle by placing files into `custom/middleware`. These files can export `preload` and `postload` diff --git a/layouts/categories/default.ejs b/layouts/categories/default.ejs index 5d832d18..ad9d4c5d 100644 --- a/layouts/categories/default.ejs +++ b/layouts/categories/default.ejs @@ -26,15 +26,6 @@ <%- include('partials/childrenList', {children, kicker: template('folder.childrenList.kicker', title)}) %> <% } %>
-
- -
- -
-
- <%- include('partials/footer', { pageType: 'document', topLevelFolder: url.split('/')[1] }) %>
diff --git a/server/formatter.js b/server/formatter.js index 6fbdd3f9..1b41aba6 100644 --- a/server/formatter.js +++ b/server/formatter.js @@ -165,6 +165,20 @@ function formatCodeContent(content) { return content } +function addImageModal(html) { + if (html.indexOf('img') === -1) { + return html; + } + + let imgModalHtml = fs.readFileSync('server/ssrComponents/imageModal.html', 'utf8') + const minimizeIconSvg = fs.readFileSync('server/ssrComponents/minimizeIcon.svg', 'utf8') + imgModalHtml = imgModalHtml.replace('', minimizeIconSvg) + + const $ = cheerio.load(html) + $('body').append(imgModalHtml) + return $('body').html() +} + function checkForTableOfContents($, aTags) { return aTags.length === 2 && // TOC links title and number aTags[0].attribs.href.match('#h.') && // the links go to a heading in the doc @@ -241,8 +255,10 @@ function convertYoutubeUrl(content) { return content } +// TODO: pass around cheerio instance vs html string to avoid loading multiple times? function getProcessedHtml(src) { let html = normalizeHtml(src) + html = addImageModal(html) html = convertYoutubeUrl(html) html = formatCode(html) html = pretty(html) diff --git a/server/ssrComponents/imageModal.html b/server/ssrComponents/imageModal.html new file mode 100644 index 00000000..fe9057d6 --- /dev/null +++ b/server/ssrComponents/imageModal.html @@ -0,0 +1,8 @@ +
+ +
+ +
+
\ No newline at end of file diff --git a/public/images/minimizeIcon.svg b/server/ssrComponents/minimizeIcon.svg similarity index 100% rename from public/images/minimizeIcon.svg rename to server/ssrComponents/minimizeIcon.svg diff --git a/styles/partials/core/_furniture.scss b/styles/partials/core/_furniture.scss index 6c734693..047507a6 100644 --- a/styles/partials/core/_furniture.scss +++ b/styles/partials/core/_furniture.scss @@ -477,7 +477,6 @@ } } - .image-modal { display: none; position: fixed; @@ -488,10 +487,19 @@ transition: display 0.2s ease 0s; } -.modal-image { +.image-modal .img-wrapper { + display: flex; + align-items: center; + align-content: center; + height: 100%; + padding: 30px; + + .modal-image { margin: auto; display: block; max-width: 100%; + cursor: default; + } } .image-modal .close { @@ -515,10 +523,4 @@ } } -.image-modal .img-wrapper { - display: flex; - align-items: center; - align-content: center; - height: 100%; - padding: 30px; -} + From a2ecd689c0015c2f8542bc828ba604d40f699a19 Mon Sep 17 00:00:00 2001 From: Rodrigo Silva Mendoza Date: Thu, 5 Aug 2021 15:48:15 -0700 Subject: [PATCH 06/12] retain list formatting --- server/formatter.js | 4 ++-- server/ssrComponents/imageModal.html | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/server/formatter.js b/server/formatter.js index 1b41aba6..600187c7 100644 --- a/server/formatter.js +++ b/server/formatter.js @@ -172,11 +172,11 @@ function addImageModal(html) { let imgModalHtml = fs.readFileSync('server/ssrComponents/imageModal.html', 'utf8') const minimizeIconSvg = fs.readFileSync('server/ssrComponents/minimizeIcon.svg', 'utf8') - imgModalHtml = imgModalHtml.replace('', minimizeIconSvg) + imgModalHtml = imgModalHtml.replace('', minimizeIconSvg) const $ = cheerio.load(html) $('body').append(imgModalHtml) - return $('body').html() + return $('head').html() + $('body').html() // include head for list style block } function checkForTableOfContents($, aTags) { diff --git a/server/ssrComponents/imageModal.html b/server/ssrComponents/imageModal.html index fe9057d6..0935c3c9 100644 --- a/server/ssrComponents/imageModal.html +++ b/server/ssrComponents/imageModal.html @@ -1,6 +1,6 @@
From 8f6fa3d72fbc31da367c441a6df8d2c24b0a69a1 Mon Sep 17 00:00:00 2001 From: Rodrigo Silva Mendoza Date: Thu, 5 Aug 2021 15:58:41 -0700 Subject: [PATCH 07/12] add tests for image modal processing --- test/unit/htmlProcessing.test.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/test/unit/htmlProcessing.test.js b/test/unit/htmlProcessing.test.js index 1d9fb8f6..b3c7ae15 100644 --- a/test/unit/htmlProcessing.test.js +++ b/test/unit/htmlProcessing.test.js @@ -179,6 +179,17 @@ describe('HTML processing', () => { }) }) + describe('image modal handling', () => { + it('adds an image modal', () => { + assert.isNotNull(testGlobal.output('image-modal')) + }) + it('replaces comment in imageModal.html with an actual svg', () => { + const $ = testGlobal.output + const imgModal = $('image-modal') + assert.isNotNull($(imgModal).find('svg')) + }) + }) + describe('comment handling', () => { it('strips comments', () => { assert.notMatch(testGlobal.processedHTML, /This comment text will not appear/) From c80d9e324a2ea08beef469585efed51001f910c6 Mon Sep 17 00:00:00 2001 From: Rodrigo Silva Mendoza Date: Thu, 5 Aug 2021 16:02:42 -0700 Subject: [PATCH 08/12] remove semicolon --- server/formatter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/formatter.js b/server/formatter.js index 600187c7..90c69acb 100644 --- a/server/formatter.js +++ b/server/formatter.js @@ -167,7 +167,7 @@ function formatCodeContent(content) { function addImageModal(html) { if (html.indexOf('img') === -1) { - return html; + return html } let imgModalHtml = fs.readFileSync('server/ssrComponents/imageModal.html', 'utf8') From bfde896dd9b0d493b253275831dd108e040a4fd2 Mon Sep 17 00:00:00 2001 From: Rodrigo Silva Mendoza Date: Mon, 9 Aug 2021 11:21:03 -0700 Subject: [PATCH 09/12] readable includes syntax Co-authored-by: Andrew Fischer --- server/formatter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/formatter.js b/server/formatter.js index 90c69acb..ad00b72a 100644 --- a/server/formatter.js +++ b/server/formatter.js @@ -166,7 +166,7 @@ function formatCodeContent(content) { } function addImageModal(html) { - if (html.indexOf('img') === -1) { + if (!html.includes('img')) { return html } From 2857e3c8bdd3b6ca9fe837adc1c12480817bd0a3 Mon Sep 17 00:00:00 2001 From: Rodrigo Silva Mendoza Date: Mon, 9 Aug 2021 15:38:50 -0700 Subject: [PATCH 10/12] entirely client side image modal impl --- custom/README.md | 2 +- layouts/categories/default.ejs | 10 +++++ layouts/partials/footer.ejs | 53 +++++++++++++++++++++++---- server/formatter.js | 29 --------------- server/ssrComponents/expandIcon.svg | 1 - server/ssrComponents/imageModal.html | 8 ---- server/ssrComponents/minimizeIcon.svg | 1 - test/unit/htmlProcessing.test.js | 29 --------------- 8 files changed, 56 insertions(+), 77 deletions(-) delete mode 100644 server/ssrComponents/expandIcon.svg delete mode 100644 server/ssrComponents/imageModal.html delete mode 100644 server/ssrComponents/minimizeIcon.svg diff --git a/custom/README.md b/custom/README.md index 963eb1e6..481d1048 100644 --- a/custom/README.md +++ b/custom/README.md @@ -91,7 +91,7 @@ Javascript function, or image path. ## Image Modal Images can opened/expanded in a modal/dialog. On hover of an image, the cursor will turn into the pointer style, and an expand button will show. Once clicked, the image will center in the page, and a minimize icon will show. Styles for the modal can be found in -`/styles/partials/core/_furniture.scss` as well as one line in `/styles/partials/core/_base.scss` that adds a pointer cursor on hover of images. The image modal is added server side, code for which can be found [here](../server/formatter.js#L168). `img` elements are wrapped in an overlay that allows for an expand button to show on hover during, code for which can be found [here](../server/formatter.js#L104). All components of the image modal that are used on the server can be found in the `/server/ssrComponents/` directory. Finally, the javascript that enables the image modal is in `/layouts/partials/footer.ejs`. +`/styles/partials/core/_furniture.scss` as well as one line in `/styles/partials/core/_base.scss` that adds a pointer cursor on hover of images. The image modal is added in the default layout for content, code for which can be found [here](../layouts/categories/default.ejs#L29). `img` elements are wrapped in an overlay that allows for an expand button to show on hover during, code for which can be found [here](../layouts/partials/footer.ejs#L138). ## Middleware Middleware can be added to the beginning or end of the request cycle by placing diff --git a/layouts/categories/default.ejs b/layouts/categories/default.ejs index ad9d4c5d..580c70a8 100644 --- a/layouts/categories/default.ejs +++ b/layouts/categories/default.ejs @@ -26,6 +26,16 @@ <%- include('partials/childrenList', {children, kicker: template('folder.childrenList.kicker', title)}) %> <% } %>
+
+ +
+ +
+ + +
<%- include('partials/footer', { pageType: 'document', topLevelFolder: url.split('/')[1] }) %>
diff --git a/layouts/partials/footer.ejs b/layouts/partials/footer.ejs index a0b78cf0..33d4495d 100644 --- a/layouts/partials/footer.ejs +++ b/layouts/partials/footer.ejs @@ -97,22 +97,59 @@ }) // no callback on this. }) - window.onload = () => { + var imgWrapper + function createImageOverlayWrapper() { + if (imgWrapper) { // cache hit + return imgWrapper + } + + // read expandIcon from image-modal where its inlined so that we + // can read it here. then remove it and strip the id since it'll be used multiple times + const expandIconSvg = document.getElementById('expandIconSvg') + expandIconSvg.remove() + expandIconSvg.id = "" + + + let wrapperDiv = document.createElement('div') + wrapperDiv.className = 'image-wrapper' + + let expandBtn = document.createElement('button') + expandBtn.setAttribute('aria-label', 'expand this image') + expandBtn.title = 'expand this image' + expandBtn.className='expand-image-btn' + expandBtn.appendChild(expandIconSvg) + + wrapperDiv.appendChild(expandBtn) + + imgWrapper = wrapperDiv // add to cache + + return wrapperDiv + } + + function wrapImageInOverlay(imgElement) { + // clone the wrapper so the original isn't mutated and we can reuse it + const overlayWrapper = createImageOverlayWrapper().cloneNode(true) + + imgElement.parentNode.insertBefore(overlayWrapper, imgElement) + overlayWrapper.appendChild(imgElement) + } + + window.onload = () => { const imgs = document.querySelectorAll('.g-main-content img'); // get all images - imgs.forEach(img => { - img.addEventListener('click', (event) => { - expandImage(img.src); - }); + + // for each image - add overlay allowing for expand btn and click handler for expansion + imgs.forEach((img, idx) => { + wrapImageInOverlay(img); + img.addEventListener('click', () => expandImage(img.src)); }); } var imgModal = document.querySelector('.image-modal') - var modalImg = document.querySelector('.image-modal .modal-image'); + var imgModalImg = document.querySelector('.image-modal .modal-image'); - function expandImage(imgSrc) { imgModal.style.display = 'block'; - modalImg.src = imgSrc; + imgModalImg.src = imgSrc; window.addEventListener('scroll', function onScroll() { window.removeEventListener('scroll', onScroll); diff --git a/server/formatter.js b/server/formatter.js index ad00b72a..f9f11b85 100644 --- a/server/formatter.js +++ b/server/formatter.js @@ -3,7 +3,6 @@ const cheerio = require('cheerio') const qs = require('querystring') const unescape = require('unescape') const hljs = require('highlight.js') -const fs = require('fs') const list = require('./list') /* Your one stop shop for all your document processing needs. */ @@ -19,12 +18,6 @@ function normalizeHtml(html) { const $p = $('p') const isClean = $('meta[name="library-html-doc"]').attr('content') === '1' - let svgString - // load svg that will be used on images. takes ~0.2ms - if (html.indexOf('img') !== -1) { - svgString = fs.readFileSync('server/ssrComponents/expandIcon.svg', 'utf8') - } - // Remove p tags in Table of Contents $p.each((index, p) => { if (p.children.length < 1) return // don't search any empty p tags @@ -99,13 +92,6 @@ function normalizeHtml(html) { $(el).attr('href', libraryDeepLink || decoded) } - // Wrap images in a div, and add an expand button with an svg inside it - // to the wrapper - if (el.tagName === 'img') { - $(el).wrap('
') - $(el).parent().append(``) - } - return el }) @@ -165,20 +151,6 @@ function formatCodeContent(content) { return content } -function addImageModal(html) { - if (!html.includes('img')) { - return html - } - - let imgModalHtml = fs.readFileSync('server/ssrComponents/imageModal.html', 'utf8') - const minimizeIconSvg = fs.readFileSync('server/ssrComponents/minimizeIcon.svg', 'utf8') - imgModalHtml = imgModalHtml.replace('', minimizeIconSvg) - - const $ = cheerio.load(html) - $('body').append(imgModalHtml) - return $('head').html() + $('body').html() // include head for list style block -} - function checkForTableOfContents($, aTags) { return aTags.length === 2 && // TOC links title and number aTags[0].attribs.href.match('#h.') && // the links go to a heading in the doc @@ -258,7 +230,6 @@ function convertYoutubeUrl(content) { // TODO: pass around cheerio instance vs html string to avoid loading multiple times? function getProcessedHtml(src) { let html = normalizeHtml(src) - html = addImageModal(html) html = convertYoutubeUrl(html) html = formatCode(html) html = pretty(html) diff --git a/server/ssrComponents/expandIcon.svg b/server/ssrComponents/expandIcon.svg deleted file mode 100644 index 389cc5ed..00000000 --- a/server/ssrComponents/expandIcon.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/server/ssrComponents/imageModal.html b/server/ssrComponents/imageModal.html deleted file mode 100644 index 0935c3c9..00000000 --- a/server/ssrComponents/imageModal.html +++ /dev/null @@ -1,8 +0,0 @@ -
- -
- -
-
\ No newline at end of file diff --git a/server/ssrComponents/minimizeIcon.svg b/server/ssrComponents/minimizeIcon.svg deleted file mode 100644 index b3f70b79..00000000 --- a/server/ssrComponents/minimizeIcon.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/test/unit/htmlProcessing.test.js b/test/unit/htmlProcessing.test.js index b3c7ae15..f7ac54ac 100644 --- a/test/unit/htmlProcessing.test.js +++ b/test/unit/htmlProcessing.test.js @@ -65,24 +65,6 @@ describe('HTML processing', () => { const widthMatch = imageWidth.attr('style').match('width') assert.isNotNull(widthMatch) }) - - it('wraps img elements in a div, and appends button containing svg to same div', () => { - const numImages = (testGlobal.rawHTML.match(/ { - assert.equal($(elem).children().length, 2) - assert.isNotNull($(elem).find('.expand-image-btn')) - assert.isNotNull($(elem).find('img')) - }) - }); }) describe('list handling', () => { @@ -179,17 +161,6 @@ describe('HTML processing', () => { }) }) - describe('image modal handling', () => { - it('adds an image modal', () => { - assert.isNotNull(testGlobal.output('image-modal')) - }) - it('replaces comment in imageModal.html with an actual svg', () => { - const $ = testGlobal.output - const imgModal = $('image-modal') - assert.isNotNull($(imgModal).find('svg')) - }) - }) - describe('comment handling', () => { it('strips comments', () => { assert.notMatch(testGlobal.processedHTML, /This comment text will not appear/) From 86b02e20a012fb60e214320b069e3a2093ab9ebf Mon Sep 17 00:00:00 2001 From: Rodrigo Silva Mendoza Date: Tue, 10 Aug 2021 07:27:59 -0700 Subject: [PATCH 11/12] remove unnecessary comment --- server/formatter.js | 1 - 1 file changed, 1 deletion(-) diff --git a/server/formatter.js b/server/formatter.js index f9f11b85..805901c1 100644 --- a/server/formatter.js +++ b/server/formatter.js @@ -227,7 +227,6 @@ function convertYoutubeUrl(content) { return content } -// TODO: pass around cheerio instance vs html string to avoid loading multiple times? function getProcessedHtml(src) { let html = normalizeHtml(src) html = convertYoutubeUrl(html) From 2c9ea2a1659bc7e977636c8dd740e1c825de69a1 Mon Sep 17 00:00:00 2001 From: Rodrigo Silva Mendoza Date: Thu, 12 Aug 2021 15:49:28 -0700 Subject: [PATCH 12/12] refactor imgModal code into a single partial --- custom/README.md | 2 +- layouts/categories/default.ejs | 11 +---- layouts/partials/footer.ejs | 71 ---------------------------- layouts/partials/imgModal.ejs | 84 ++++++++++++++++++++++++++++++++++ 4 files changed, 86 insertions(+), 82 deletions(-) create mode 100644 layouts/partials/imgModal.ejs diff --git a/custom/README.md b/custom/README.md index 481d1048..c54c2567 100644 --- a/custom/README.md +++ b/custom/README.md @@ -91,7 +91,7 @@ Javascript function, or image path. ## Image Modal Images can opened/expanded in a modal/dialog. On hover of an image, the cursor will turn into the pointer style, and an expand button will show. Once clicked, the image will center in the page, and a minimize icon will show. Styles for the modal can be found in -`/styles/partials/core/_furniture.scss` as well as one line in `/styles/partials/core/_base.scss` that adds a pointer cursor on hover of images. The image modal is added in the default layout for content, code for which can be found [here](../layouts/categories/default.ejs#L29). `img` elements are wrapped in an overlay that allows for an expand button to show on hover during, code for which can be found [here](../layouts/partials/footer.ejs#L138). +`/styles/partials/core/_furniture.scss` as well as one line in `/styles/partials/core/_base.scss` that adds a pointer cursor on hover of images. All HTML/JS for the Image Modal is included in the [`/layouts/partials/imgModal.ejs`](../layouts/partials/imgModal.ejs) file. The Image Modal is then imported in the default layout for content, code for which can be found [here](../layouts/categories/default.ejs#L29). ## Middleware Middleware can be added to the beginning or end of the request cycle by placing diff --git a/layouts/categories/default.ejs b/layouts/categories/default.ejs index 580c70a8..28a0021e 100644 --- a/layouts/categories/default.ejs +++ b/layouts/categories/default.ejs @@ -26,16 +26,7 @@ <%- include('partials/childrenList', {children, kicker: template('folder.childrenList.kicker', title)}) %> <% } %> -
- -
- -
- - -
+ <%- include('partials/imgModal') %> <%- include('partials/footer', { pageType: 'document', topLevelFolder: url.split('/')[1] }) %> diff --git a/layouts/partials/footer.ejs b/layouts/partials/footer.ejs index 33d4495d..1ccff568 100644 --- a/layouts/partials/footer.ejs +++ b/layouts/partials/footer.ejs @@ -97,75 +97,4 @@ }) // no callback on this. }) - var imgWrapper - function createImageOverlayWrapper() { - if (imgWrapper) { // cache hit - return imgWrapper - } - - // read expandIcon from image-modal where its inlined so that we - // can read it here. then remove it and strip the id since it'll be used multiple times - const expandIconSvg = document.getElementById('expandIconSvg') - expandIconSvg.remove() - expandIconSvg.id = "" - - - let wrapperDiv = document.createElement('div') - wrapperDiv.className = 'image-wrapper' - - let expandBtn = document.createElement('button') - expandBtn.setAttribute('aria-label', 'expand this image') - expandBtn.title = 'expand this image' - expandBtn.className='expand-image-btn' - expandBtn.appendChild(expandIconSvg) - - wrapperDiv.appendChild(expandBtn) - - imgWrapper = wrapperDiv // add to cache - - return wrapperDiv - } - - function wrapImageInOverlay(imgElement) { - // clone the wrapper so the original isn't mutated and we can reuse it - const overlayWrapper = createImageOverlayWrapper().cloneNode(true) - - imgElement.parentNode.insertBefore(overlayWrapper, imgElement) - overlayWrapper.appendChild(imgElement) - } - - window.onload = () => { - const imgs = document.querySelectorAll('.g-main-content img'); // get all images - - // for each image - add overlay allowing for expand btn and click handler for expansion - imgs.forEach((img, idx) => { - wrapImageInOverlay(img); - img.addEventListener('click', () => expandImage(img.src)); - }); - } - - var imgModal = document.querySelector('.image-modal') - var imgModalImg = document.querySelector('.image-modal .modal-image'); - - function expandImage(imgSrc) { - imgModal.style.display = 'block'; - imgModalImg.src = imgSrc; - - window.addEventListener('scroll', function onScroll() { - window.removeEventListener('scroll', onScroll); - imgModal.style.display='none'; - }); - }; - - var modalCloseBtn = document.querySelector('.image-modal .close'); - modalCloseBtn.addEventListener('click', () => { - imgModal.style.display = 'none'; - }); - - /* keydown listener so img modal can be closed with Esc key */ - document.addEventListener('keydown', (event) => { - if (event.key === 'Escape' && imgModal.style.display === 'block') { - imgModal.style.display = 'none'; - } - }); diff --git a/layouts/partials/imgModal.ejs b/layouts/partials/imgModal.ejs new file mode 100644 index 00000000..21291e78 --- /dev/null +++ b/layouts/partials/imgModal.ejs @@ -0,0 +1,84 @@ +
+ +
+ +
+ + +
+ +