From b03390d237427a2b9cfee55949cb1da62f1a6856 Mon Sep 17 00:00:00 2001 From: Jimmy Cai Date: Sat, 29 Oct 2022 17:14:19 +0200 Subject: Revert "Merge branch 'canary' into master" (#712) Revert "Merge branch 'canary' into master (#711)" This reverts commit 8a597a5c9c861d2bce22a5e06c7667cd9c8e263a. --- assets/scss/partials/article.scss | 1 + assets/scss/partials/highlight/dark.scss | 2 +- assets/scss/partials/layout/article.scss | 48 +++---- assets/scss/partials/layout/search.scss | 4 +- assets/scss/partials/sidebar.scss | 6 +- assets/ts/codeblock.ts | 28 ---- assets/ts/color.ts | 63 ++++++++ assets/ts/gallery.ts | 240 +++++++++++++++++++++---------- assets/ts/main.ts | 69 ++++++++- 9 files changed, 322 insertions(+), 139 deletions(-) delete mode 100644 assets/ts/codeblock.ts create mode 100644 assets/ts/color.ts (limited to 'assets') diff --git a/assets/scss/partials/article.scss b/assets/scss/partials/article.scss index a041e9b..f085ff0 100644 --- a/assets/scss/partials/article.scss +++ b/assets/scss/partials/article.scss @@ -192,6 +192,7 @@ .article-preview { font-size: 1.4rem; color: var(--card-text-color-tertiary); + margin-top: 10px; line-height: 1.5; } } diff --git a/assets/scss/partials/highlight/dark.scss b/assets/scss/partials/highlight/dark.scss index 3ea6c56..0d3f330 100644 --- a/assets/scss/partials/highlight/dark.scss +++ b/assets/scss/partials/highlight/dark.scss @@ -11,4 +11,4 @@ $text-color: $color; $name-color: #a6e22e; $literal-color: #e6db74; -@import "common.scss"; \ No newline at end of file +@import "common.scss"; diff --git a/assets/scss/partials/layout/article.scss b/assets/scss/partials/layout/article.scss index ba06c11..97c7e77 100644 --- a/assets/scss/partials/layout/article.scss +++ b/assets/scss/partials/layout/article.scss @@ -286,12 +286,10 @@ line-height: 1.428571429; word-break: break-all; padding: var(--card-padding); - // keep Codeblocks LTR [dir="rtl"] & { direction: ltr; } - code { color: unset; border: none; @@ -305,11 +303,15 @@ padding: var(--card-padding); position: relative; + &:hover { + .copyCodeButton { + opacity: 1; + } + } // keep Codeblocks LTR [dir="rtl"] & { direction: ltr; } - pre { margin: initial; padding: 0; @@ -318,30 +320,20 @@ } } - .codeblock { - header { - background-color: var(--card-background-selected); - padding: 5px var(--card-padding); - display: flex; - justify-content: space-between; - box-shadow: var(--shadow-l1); - - span { - text-transform: uppercase; - font-weight: bold; - color: var(--card-text-color-secondary); - } - } - - .codeblock-copy { - cursor: pointer; - background-color: transparent; - border: none; - padding: 8px 16px; - color: var(--card-text-color-secondary); - font-size: 14px; - font-weight: bold; - } + .copyCodeButton { + position: absolute; + top: calc(var(--card-padding)); + right: calc(var(--card-padding)); + background: var(--card-background); + border: none; + box-shadow: var(--shadow-l2); + border-radius: var(--tag-border-radius); + padding: 8px 16px; + color: var(--card-text-color-main); + cursor: pointer; + font-size: 14px; + opacity: 0; + transition: opacity 0.3s ease; } .table-wrapper { @@ -410,7 +402,7 @@ /// Negative margins blockquote, figure, - .codeblock, + .highlight, pre, .gallery, .video-wrapper, diff --git a/assets/scss/partials/layout/search.scss b/assets/scss/partials/layout/search.scss index cbf15b7..89cdcef 100644 --- a/assets/scss/partials/layout/search.scss +++ b/assets/scss/partials/layout/search.scss @@ -31,7 +31,6 @@ input { padding: 40px 20px 20px; - padding-inline-end: var(--button-size); border-radius: var(--card-border-radius); background-color: var(--card-background); box-shadow: var(--shadow-l1); @@ -79,4 +78,5 @@ height: 20px; } } -} + +} \ No newline at end of file diff --git a/assets/scss/partials/sidebar.scss b/assets/scss/partials/sidebar.scss index 75332a7..95310ca 100644 --- a/assets/scss/partials/sidebar.scss +++ b/assets/scss/partials/sidebar.scss @@ -11,6 +11,7 @@ flex-direction: column; flex-shrink: 0; align-self: stretch; + gap: var(--sidebar-element-separation); max-width: none; width: 100%; position: relative; @@ -64,11 +65,6 @@ } } } - - .social-menu, - .menu { - margin-top: var(--sidebar-element-separation); - } } .right-sidebar { diff --git a/assets/ts/codeblock.ts b/assets/ts/codeblock.ts deleted file mode 100644 index 08c3328..0000000 --- a/assets/ts/codeblock.ts +++ /dev/null @@ -1,28 +0,0 @@ -/** - * Copy button for code blocks -*/ -export default () => { - const copyButtons = document.querySelectorAll('.codeblock-copy'); - copyButtons.forEach(button => { - const codeblockID = button.getAttribute('data-id'), - copyText = button.textContent, - copiedText = button.getAttribute('data-copied-text'); - if (!codeblockID) return; - button.addEventListener('click', (e) => { - e.preventDefault(); - const codeblock = document.getElementById(codeblockID) as HTMLElement; - if (!codeblockID) return; - navigator.clipboard.writeText(codeblock.textContent) - .then(() => { - button.textContent = copiedText; - setTimeout(() => { - button.textContent = copyText; - }, 1000); - }) - .catch(err => { - alert(err) - console.log('Something went wrong', err); - }); - }, false); - }); -} \ No newline at end of file diff --git a/assets/ts/color.ts b/assets/ts/color.ts new file mode 100644 index 0000000..50581d1 --- /dev/null +++ b/assets/ts/color.ts @@ -0,0 +1,63 @@ +interface colorScheme { + hash: string, /// Regenerate color scheme when the image hash is changed + DarkMuted: { + hex: string, + rgb: Number[], + bodyTextColor: string + }, + Vibrant: { + hex: string, + rgb: Number[], + bodyTextColor: string + } +} + +let colorsCache: { [key: string]: colorScheme } = {}; + +if (localStorage.hasOwnProperty('StackColorsCache')) { + try { + colorsCache = JSON.parse(localStorage.getItem('StackColorsCache')); + } + catch (e) { + colorsCache = {}; + } +} + +async function getColor(key: string, hash: string, imageURL: string) { + if (!key) { + /** + * If no key is provided, do not cache the result + */ + return await Vibrant.from(imageURL).getPalette(); + } + + if (!colorsCache.hasOwnProperty(key) || colorsCache[key].hash !== hash) { + /** + * If key is provided, but not found in cache, or the hash mismatches => Regenerate color scheme + */ + const palette = await Vibrant.from(imageURL).getPalette(); + + colorsCache[key] = { + hash: hash, + Vibrant: { + hex: palette.Vibrant.hex, + rgb: palette.Vibrant.rgb, + bodyTextColor: palette.Vibrant.bodyTextColor + }, + DarkMuted: { + hex: palette.DarkMuted.hex, + rgb: palette.DarkMuted.rgb, + bodyTextColor: palette.DarkMuted.bodyTextColor + } + } + + /* Save the result in localStorage */ + localStorage.setItem('StackColorsCache', JSON.stringify(colorsCache)); + } + + return colorsCache[key]; +} + +export { + getColor +} \ No newline at end of file diff --git a/assets/ts/gallery.ts b/assets/ts/gallery.ts index e0124d1..9840f1e 100644 --- a/assets/ts/gallery.ts +++ b/assets/ts/gallery.ts @@ -1,92 +1,186 @@ -const wrap = (figures: HTMLElement[]) => { - const galleryContainer = document.createElement('div'); - galleryContainer.className = 'gallery'; +declare global { + interface Window { + PhotoSwipe: any; + PhotoSwipeUI_Default: any + } +} - const parentNode = figures[0].parentNode, - first = figures[0]; +interface PhotoSwipeItem { + w: number; + h: number; + src: string; + msrc: string; + title?: string; + el: HTMLElement; +} - parentNode.insertBefore(galleryContainer, first) +class StackGallery { + private galleryUID: number; + private items: PhotoSwipeItem[] = []; + + constructor(container: HTMLElement, galleryUID = 1) { + if (window.PhotoSwipe == undefined || window.PhotoSwipeUI_Default == undefined) { + console.error("PhotoSwipe lib not loaded."); + return; + } - for (const figure of figures) { - galleryContainer.appendChild(figure); + this.galleryUID = galleryUID; + + StackGallery.createGallery(container); + this.loadItems(container); + this.bindClick(); } -} -export default (container: HTMLElement) => { - /// The process of wrapping image with figure tag is done using JavaScript instead of only Hugo markdown render hook - /// because it can not detect whether image is being wrapped by a link or not - /// and it lead to a invalid HTML construction (
) - const images = container.querySelectorAll('img.gallery-image') as NodeListOf; - for (const img of Array.from(images)) { - /// Images are wrapped with figure tag if the paragraph has only images without texts - /// This is done to allow inline images within paragraphs - const paragraph = img.closest('p'); - - if (!paragraph || !container.contains(paragraph)) continue; - - if (paragraph.textContent.trim() == '') { - /// Once we insert figcaption, this check no longer works - /// So we add a class to paragraph to mark it - paragraph.classList.add('no-text'); + private loadItems(container: HTMLElement) { + this.items = []; + + const figures = container.querySelectorAll('figure.gallery-image'); + + for (const el of figures) { + const figcaption = el.querySelector('figcaption'), + img = el.querySelector('img'); + + let aux: PhotoSwipeItem = { + w: parseInt(img.getAttribute('width')), + h: parseInt(img.getAttribute('height')), + src: img.src, + msrc: img.getAttribute('data-thumb') || img.src, + el: el + } + + if (figcaption) { + aux.title = figcaption.innerHTML; + } + + this.items.push(aux); } + } + + public static createGallery(container: HTMLElement) { + /// The process of wrapping image with figure tag is done using JavaScript instead of only Hugo markdown render hook + /// because it can not detect whether image is being wrapped by a link or not + /// and it lead to a invalid HTML construction (
) + + const images = container.querySelectorAll('img.gallery-image'); + for (const img of Array.from(images)) { + /// Images are wrapped with figure tag if the paragraph has only images without texts + /// This is done to allow inline images within paragraphs + const paragraph = img.closest('p'); + + if (!paragraph || !container.contains(paragraph)) continue; + + if (paragraph.textContent.trim() == '') { + /// Once we insert figcaption, this check no longer works + /// So we add a class to paragraph to mark it + paragraph.classList.add('no-text'); + } + + let isNewLineImage = paragraph.classList.contains('no-text'); + if (!isNewLineImage) continue; - let isNewLineImage = paragraph.classList.contains('no-text'); - if (!isNewLineImage) continue; + const hasLink = img.parentElement.tagName == 'A'; - const hasLink = img.parentElement.tagName == 'A'; + let el: HTMLElement = img; + /// Wrap image with figure tag, with flex-grow and flex-basis values extracted from img's data attributes + const figure = document.createElement('figure'); + figure.style.setProperty('flex-grow', img.getAttribute('data-flex-grow') || '1'); + figure.style.setProperty('flex-basis', img.getAttribute('data-flex-basis') || '0'); + if (hasLink) { + /// Wrap if it exists + el = img.parentElement; + } + el.parentElement.insertBefore(figure, el); + figure.appendChild(el); - let el: HTMLElement = img; - /// Wrap image with figure tag, with flex-grow and flex-basis values extracted from img's data attributes - const figure = document.createElement('figure'); - figure.style.setProperty('flex-grow', img.getAttribute('data-flex-grow') || '1'); - figure.style.setProperty('flex-basis', img.getAttribute('data-flex-basis') || '0'); - if (hasLink) { - /// Wrap if it exists - el = img.parentElement; + /// Add figcaption if it exists + if (img.hasAttribute('alt')) { + const figcaption = document.createElement('figcaption'); + figcaption.innerText = img.getAttribute('alt'); + figure.appendChild(figcaption); + } + + /// Wrap img tag with tag if image was not wrapped by tag + if (!hasLink) { + figure.className = 'gallery-image'; + + const a = document.createElement('a'); + a.href = img.src; + a.setAttribute('target', '_blank'); + img.parentNode.insertBefore(a, img); + a.appendChild(img); + } } - el.parentElement.insertBefore(figure, el); - figure.appendChild(el); - - /// Add figcaption if it exists - if (img.hasAttribute('alt')) { - const figcaption = document.createElement('figcaption'); - figcaption.innerText = img.getAttribute('alt'); - figure.appendChild(figcaption); + + const figuresEl = container.querySelectorAll('figure.gallery-image'); + + let currentGallery = []; + + for (const figure of figuresEl) { + if (!currentGallery.length) { + /// First iteration + currentGallery = [figure]; + } + else if (figure.previousElementSibling === currentGallery[currentGallery.length - 1]) { + /// Adjacent figures + currentGallery.push(figure); + } + else if (currentGallery.length) { + /// End gallery + StackGallery.wrap(currentGallery); + currentGallery = [figure]; + } } - /// Wrap img tag with tag if image was not wrapped by tag - if (!hasLink) { - figure.className = 'gallery-image'; - - const a = document.createElement('a'); - a.href = img.src; - a.setAttribute('target', '_blank'); - a.setAttribute('data-pswp-width', img.width.toString()); - a.setAttribute('data-pswp-height', img.height.toString()); - img.parentNode.insertBefore(a, img); - a.appendChild(img); + if (currentGallery.length > 0) { + StackGallery.wrap(currentGallery); } } - const figuresEl = container.querySelectorAll('figure.gallery-image') as NodeListOf; - let currentGallery = []; - for (const figure of Array.from(figuresEl)) { - if (!currentGallery.length) { - /// First iteration - currentGallery = [figure]; - } - else if (figure.previousElementSibling === currentGallery[currentGallery.length - 1]) { - /// Adjacent figures - currentGallery.push(figure); - } - else if (currentGallery.length) { - /// End gallery - wrap(currentGallery); - currentGallery = [figure]; + /** + * Wrap adjacent figure tags with div.gallery + * @param figures + */ + public static wrap(figures: HTMLElement[]) { + const galleryContainer = document.createElement('div'); + galleryContainer.className = 'gallery'; + + const parentNode = figures[0].parentNode, + first = figures[0]; + + parentNode.insertBefore(galleryContainer, first) + + for (const figure of figures) { + galleryContainer.appendChild(figure); } } - if (currentGallery.length > 0) { - wrap(currentGallery); + public open(index: number) { + const pswp = document.querySelector('.pswp') as HTMLDivElement; + const ps = new window.PhotoSwipe(pswp, window.PhotoSwipeUI_Default, this.items, { + index: index, + galleryUID: this.galleryUID, + getThumbBoundsFn: (index) => { + const thumbnail = this.items[index].el.getElementsByTagName('img')[0], + pageYScroll = window.pageYOffset || document.documentElement.scrollTop, + rect = thumbnail.getBoundingClientRect(); + + return { x: rect.left, y: rect.top + pageYScroll, w: rect.width }; + } + }); + + ps.init(); } -}; \ No newline at end of file + + private bindClick() { + for (const [index, item] of this.items.entries()) { + const a = item.el.querySelector('a'); + + a.addEventListener('click', (e) => { + e.preventDefault(); + this.open(index); + }) + } + } +} + +export default StackGallery; \ No newline at end of file diff --git a/assets/ts/main.ts b/assets/ts/main.ts index 0668c7c..f3160ae 100644 --- a/assets/ts/main.ts +++ b/assets/ts/main.ts @@ -5,7 +5,8 @@ * @website: https://jimmycai.com * @link: https://github.com/CaiJimmy/hugo-theme-stack */ -import StackCodeBlock from "ts/codeblock"; +import StackGallery from "ts/gallery"; +import { getColor } from 'ts/color'; import menu from 'ts/menu'; import createElement from 'ts/createElement'; import StackColorScheme from 'ts/colorScheme'; @@ -21,12 +22,76 @@ let Stack = { const articleContent = document.querySelector('.article-content') as HTMLElement; if (articleContent) { + new StackGallery(articleContent); setupSmoothAnchors(); setupScrollspy(); } + /** + * Add linear gradient background to tile style article + */ + const articleTile = document.querySelector('.article-list--tile'); + if (articleTile) { + let observer = new IntersectionObserver(async (entries, observer) => { + entries.forEach(entry => { + if (!entry.isIntersecting) return; + observer.unobserve(entry.target); + + const articles = entry.target.querySelectorAll('article.has-image'); + articles.forEach(async articles => { + const image = articles.querySelector('img'), + imageURL = image.src, + key = image.getAttribute('data-key'), + hash = image.getAttribute('data-hash'), + articleDetails: HTMLDivElement = articles.querySelector('.article-details'); + + const colors = await getColor(key, hash, imageURL); + + articleDetails.style.background = ` + linear-gradient(0deg, + rgba(${colors.DarkMuted.rgb[0]}, ${colors.DarkMuted.rgb[1]}, ${colors.DarkMuted.rgb[2]}, 0.5) 0%, + rgba(${colors.Vibrant.rgb[0]}, ${colors.Vibrant.rgb[1]}, ${colors.Vibrant.rgb[2]}, 0.75) 100%)`; + }) + }) + }); + + observer.observe(articleTile) + } + + + /** + * Add copy button to code block + */ + const highlights = document.querySelectorAll('.article-content div.highlight'); + const copyText = `Copy`, + copiedText = `Copied!`; + + highlights.forEach(highlight => { + const copyButton = document.createElement('button'); + copyButton.innerHTML = copyText; + copyButton.classList.add('copyCodeButton'); + highlight.appendChild(copyButton); + + const codeBlock = highlight.querySelector('code[data-lang]'); + if (!codeBlock) return; + + copyButton.addEventListener('click', () => { + navigator.clipboard.writeText(codeBlock.textContent) + .then(() => { + copyButton.textContent = copiedText; + + setTimeout(() => { + copyButton.textContent = copyText; + }, 1000); + }) + .catch(err => { + alert(err) + console.log('Something went wrong', err); + }); + }); + }); + new StackColorScheme(document.getElementById('dark-mode-toggle')); - StackCodeBlock(); } } -- cgit v1.2.3