diff options
Diffstat (limited to 'assets')
-rw-r--r-- | assets/scss/partials/article.scss | 1 | ||||
-rw-r--r-- | assets/scss/partials/highlight/dark.scss | 2 | ||||
-rw-r--r-- | assets/scss/partials/layout/article.scss | 48 | ||||
-rw-r--r-- | assets/scss/partials/layout/search.scss | 4 | ||||
-rw-r--r-- | assets/scss/partials/sidebar.scss | 6 | ||||
-rw-r--r-- | assets/ts/codeblock.ts | 28 | ||||
-rw-r--r-- | assets/ts/color.ts | 63 | ||||
-rw-r--r-- | assets/ts/gallery.ts | 240 | ||||
-rw-r--r-- | assets/ts/main.ts | 69 |
9 files changed, 139 insertions, 322 deletions
diff --git a/assets/scss/partials/article.scss b/assets/scss/partials/article.scss index f085ff0..a041e9b 100644 --- a/assets/scss/partials/article.scss +++ b/assets/scss/partials/article.scss @@ -192,7 +192,6 @@ .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 0d3f330..3ea6c56 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"; +@import "common.scss";
\ No newline at end of file diff --git a/assets/scss/partials/layout/article.scss b/assets/scss/partials/layout/article.scss index 97c7e77..ba06c11 100644 --- a/assets/scss/partials/layout/article.scss +++ b/assets/scss/partials/layout/article.scss @@ -286,10 +286,12 @@ line-height: 1.428571429; word-break: break-all; padding: var(--card-padding); + // keep Codeblocks LTR [dir="rtl"] & { direction: ltr; } + code { color: unset; border: none; @@ -303,15 +305,11 @@ padding: var(--card-padding); position: relative; - &:hover { - .copyCodeButton { - opacity: 1; - } - } // keep Codeblocks LTR [dir="rtl"] & { direction: ltr; } + pre { margin: initial; padding: 0; @@ -320,20 +318,30 @@ } } - .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; + .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; + } } .table-wrapper { @@ -402,7 +410,7 @@ /// Negative margins blockquote, figure, - .highlight, + .codeblock, pre, .gallery, .video-wrapper, diff --git a/assets/scss/partials/layout/search.scss b/assets/scss/partials/layout/search.scss index 89cdcef..cbf15b7 100644 --- a/assets/scss/partials/layout/search.scss +++ b/assets/scss/partials/layout/search.scss @@ -31,6 +31,7 @@ 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); @@ -78,5 +79,4 @@ height: 20px; } } - -}
\ No newline at end of file +} diff --git a/assets/scss/partials/sidebar.scss b/assets/scss/partials/sidebar.scss index 95310ca..75332a7 100644 --- a/assets/scss/partials/sidebar.scss +++ b/assets/scss/partials/sidebar.scss @@ -11,7 +11,6 @@ flex-direction: column; flex-shrink: 0; align-self: stretch; - gap: var(--sidebar-element-separation); max-width: none; width: 100%; position: relative; @@ -65,6 +64,11 @@ } } } + + .social-menu, + .menu { + margin-top: var(--sidebar-element-separation); + } } .right-sidebar { diff --git a/assets/ts/codeblock.ts b/assets/ts/codeblock.ts new file mode 100644 index 0000000..08c3328 --- /dev/null +++ b/assets/ts/codeblock.ts @@ -0,0 +1,28 @@ +/** + * 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 deleted file mode 100644 index 50581d1..0000000 --- a/assets/ts/color.ts +++ /dev/null @@ -1,63 +0,0 @@ -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 9840f1e..e0124d1 100644 --- a/assets/ts/gallery.ts +++ b/assets/ts/gallery.ts @@ -1,186 +1,92 @@ -declare global { - interface Window { - PhotoSwipe: any; - PhotoSwipeUI_Default: any - } -} +const wrap = (figures: HTMLElement[]) => { + const galleryContainer = document.createElement('div'); + galleryContainer.className = 'gallery'; -interface PhotoSwipeItem { - w: number; - h: number; - src: string; - msrc: string; - title?: string; - el: HTMLElement; -} + const parentNode = figures[0].parentNode, + first = figures[0]; -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; - } + parentNode.insertBefore(galleryContainer, first) - this.galleryUID = galleryUID; - - StackGallery.createGallery(container); - this.loadItems(container); - this.bindClick(); + for (const figure of figures) { + galleryContainer.appendChild(figure); } +} - 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); +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 (<a><figure><img></figure></a>) + const images = container.querySelectorAll('img.gallery-image') as NodeListOf<HTMLImageElement>; + 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'); } - } - - 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 (<a><figure><img></figure></a>) - - 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; - const hasLink = img.parentElement.tagName == 'A'; + let isNewLineImage = paragraph.classList.contains('no-text'); + if (!isNewLineImage) continue; - 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 <a> if it exists - el = img.parentElement; - } - el.parentElement.insertBefore(figure, el); - figure.appendChild(el); + const hasLink = img.parentElement.tagName == 'A'; - /// 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 <a> tag if image was not wrapped by <a> 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); - } + 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 <a> if it exists + el = img.parentElement; } - - 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]; - } + 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); } - if (currentGallery.length > 0) { - StackGallery.wrap(currentGallery); + /// Wrap img tag with <a> tag if image was not wrapped by <a> 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); } } - /** - * 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); + const figuresEl = container.querySelectorAll('figure.gallery-image') as NodeListOf<HTMLElement>; + let currentGallery = []; + for (const figure of Array.from(figuresEl)) { + if (!currentGallery.length) { + /// First iteration + currentGallery = [figure]; } - } - - 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(); - } - - 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); - }) + else if (figure.previousElementSibling === currentGallery[currentGallery.length - 1]) { + /// Adjacent figures + currentGallery.push(figure); + } + else if (currentGallery.length) { + /// End gallery + wrap(currentGallery); + currentGallery = [figure]; } } -} -export default StackGallery;
\ No newline at end of file + if (currentGallery.length > 0) { + wrap(currentGallery); + } +};
\ No newline at end of file diff --git a/assets/ts/main.ts b/assets/ts/main.ts index f3160ae..0668c7c 100644 --- a/assets/ts/main.ts +++ b/assets/ts/main.ts @@ -5,8 +5,7 @@ * @website: https://jimmycai.com * @link: https://github.com/CaiJimmy/hugo-theme-stack */ -import StackGallery from "ts/gallery"; -import { getColor } from 'ts/color'; +import StackCodeBlock from "ts/codeblock"; import menu from 'ts/menu'; import createElement from 'ts/createElement'; import StackColorScheme from 'ts/colorScheme'; @@ -22,76 +21,12 @@ 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(); } } |