diff --git a/Mac/MainWindow/Detail/page.html b/Mac/MainWindow/Detail/page.html index 97e7c4541..7482a74b0 100644 --- a/Mac/MainWindow/Detail/page.html +++ b/Mac/MainWindow/Detail/page.html @@ -3,6 +3,7 @@ + diff --git a/Mac/MainWindow/Detail/styleSheet.css b/Mac/MainWindow/Detail/styleSheet.css index 107a7cb7e..c39d376ce 100644 --- a/Mac/MainWindow/Detail/styleSheet.css +++ b/Mac/MainWindow/Detail/styleSheet.css @@ -181,3 +181,51 @@ img[src*="feedblitz"], img[src*="share-buttons"] { display: none !important; } + + +/* Newsfoot specific styles. Structural styles come first, theme styles second */ +.newsfoot-footnote-container { + position: relative; + display: inline-block; +} +.newsfoot-footnote-popover { + position: absolute; + display: block; + padding: 0em 1em; + margin: 1em; + left: -11em; + right: -11em; + max-width: none; + border-radius: 0.3em; + box-sizing: border-box; +} +a.footnote { + display: inline-block; + text-decoration: none; + padding: 0.05em 0.75em; + border-radius: 1em; + min-width: 1em; + text-align: center; + font-size: 0.8em; + line-height: 1em; + position:relative; + top: -0.1em; +} + +/* light / default */ +.newsfoot-footnote-popover { + background: #fafafa; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.5); + color: black; + border: 1px solid #ccc; +} +body a.footnote, +body a.footnote:visited { + background: #aaa; + color: white; + transition: background-color 200ms ease-out; +} +a.footnote:hover { + background: #666; + transition: background-color 200ms ease-out; +} diff --git a/Shared/Article Rendering/newsfoot.js b/Shared/Article Rendering/newsfoot.js index ca725c19e..7374610e7 100644 --- a/Shared/Article Rendering/newsfoot.js +++ b/Shared/Article Rendering/newsfoot.js @@ -1,119 +1,122 @@ + (function () { + // @ts-check + /** @param {Node | null} el */ + const remove = (el) => { if (el) el.parentElement.removeChild(el) }; -// @ts-check -/** @param {Node | null} el */ -const remove = (el) => { if (el) el.parentElement.removeChild(el) }; + const stripPx = (s) => +s.slice(0, -2); -const stripPx = (s) => +s.slice(0, -2); + /** @param {string} tag + * @param {string} cls + * @returns HTMLElement + */ + function newEl(tag, cls) { + const el = document.createElement(tag); + el.classList.add(cls); + return el; + } -/** @param {string} tag - * @param {string} cls - * @returns HTMLElement - */ -function newEl(tag, cls) { - const el = document.createElement(tag); - el.classList.add(cls); - return el; -} + /** @type {(fn: (...args: T) => void, t: number) => ((...args: T) => void)} */ + function debounce(f, ms) { + let t = Date.now(); + return (...args) => { + const now = Date.now(); + if (now - t < ms) return; + t = now; + f(...args); + }; + } -/** @type {(fn: (...args: T) => void, t: number) => ((...args: T) => void)} */ -function debounce(f, ms) { - let t = Date.now(); - return (...args) => { - const now = Date.now(); - if (now - t < ms) return; - t = now; - f(...args); - }; -} + const clsPrefix = "newsfoot-footnote-"; + const CONTAINER_CLS = `${clsPrefix}container`; + const POPOVER_CLS = `${clsPrefix}popover`; -const clsPrefix = "newsfoot-footnote-"; -const CONTAINER_CLS = `${clsPrefix}container`; -const POPOVER_CLS = `${clsPrefix}popover`; - -/** - * @param {Node} content - * @returns {HTMLElement} - */ -function footnoteMarkup(content) { - const popover = newEl("div", POPOVER_CLS); - popover.appendChild(content); - return popover; -} - -class Footnote { /** * @param {Node} content - * @param {Element} fnref + * @returns {HTMLElement} */ - constructor(content, fnref) { - this.popover = footnoteMarkup(content); - this.style = window.getComputedStyle(this.popover); - this.fnref = fnref; - this.fnref.closest(`.${CONTAINER_CLS}`).appendChild(this.popover); - this.reposition(); - - /** @type {(ev:MouseEvent) => void} */ - this.clickoutHandler = (ev) => { - if (!(ev.target instanceof Element)) return; - if (ev.target.closest(`.${POPOVER_CLS}`) === this.popover) return; - this.cleanup(); - } - document.addEventListener("click", this.clickoutHandler, {capture: true}); - - this.resizeHandler = debounce(() => this.reposition(), 20); - window.addEventListener("resize", this.resizeHandler); + function footnoteMarkup(content) { + const popover = newEl("div", POPOVER_CLS); + popover.appendChild(content); + return popover; } - - cleanup() { - remove(this.popover); - document.removeEventListener("click", this.clickoutHandler, {capture: true}); - window.removeEventListener("resize", this.resizeHandler); - delete this.popover; - delete this.clickoutHandler; - delete this.resizeHandler; - } - - reposition() { - const refRect = this.fnref.getBoundingClientRect(); - const center = refRect.left + (refRect.width / 2); - const popoverHalfWidth = this.popover.clientWidth / 2; - const marginLeft = stripPx(this.style.marginLeft); - const marginRight = stripPx(this.style.marginRight); - - let offset = 0; - if (center + popoverHalfWidth + marginRight > window.innerWidth) { - offset = -((center + popoverHalfWidth + marginRight) - window.innerWidth); - } - else if (center - (popoverHalfWidth + marginLeft) < 0) { - offset = (popoverHalfWidth + marginLeft) - center; - } - this.popover.style.transform = `translate(${offset}px)`; - } -} -/** @param {Node} n */ -function fragFromContents(n) { - const frag = document.createDocumentFragment(); - n.childNodes.forEach((ch) => frag.appendChild(ch)); - return frag; -} - -/** @param {HTMLAnchorElement} a */ -function installContainer(a) { - if (!a.parentElement.matches(`.${CONTAINER_CLS}`)) { - const container = newEl("div", CONTAINER_CLS); - a.parentElement.insertBefore(container, a); - container.appendChild(a); + class Footnote { + /** + * @param {Node} content + * @param {Element} fnref + */ + constructor(content, fnref) { + this.popover = footnoteMarkup(content); + this.style = window.getComputedStyle(this.popover); + this.fnref = fnref; + this.fnref.closest(`.${CONTAINER_CLS}`).appendChild(this.popover); + this.reposition(); + + /** @type {(ev:MouseEvent) => void} */ + this.clickoutHandler = (ev) => { + if (!(ev.target instanceof Element)) return; + if (ev.target.closest(`.${POPOVER_CLS}`) === this.popover) return; + this.cleanup(); + } + document.addEventListener("click", this.clickoutHandler, {capture: true}); + + this.resizeHandler = debounce(() => this.reposition(), 20); + window.addEventListener("resize", this.resizeHandler); + } + + cleanup() { + remove(this.popover); + document.removeEventListener("click", this.clickoutHandler, {capture: true}); + window.removeEventListener("resize", this.resizeHandler); + delete this.popover; + delete this.clickoutHandler; + delete this.resizeHandler; + } + + reposition() { + const refRect = this.fnref.getBoundingClientRect(); + const center = refRect.left + (refRect.width / 2); + const popoverHalfWidth = this.popover.clientWidth / 2; + const marginLeft = stripPx(this.style.marginLeft); + const marginRight = stripPx(this.style.marginRight); + + let offset = 0; + if (center + popoverHalfWidth + marginRight > window.innerWidth) { + offset = -((center + popoverHalfWidth + marginRight) - window.innerWidth); + } + else if (center - (popoverHalfWidth + marginLeft) < 0) { + offset = (popoverHalfWidth + marginLeft) - center; + } + this.popover.style.transform = `translate(${offset}px)`; + } } -} -document.addEventListener("click", (ev) => { - if (!(ev.target && ev.target instanceof HTMLAnchorElement)) return; - if (!ev.target.matches(".footnote")) return; - ev.preventDefault(); - - const content = document.querySelector(`[id='${ev.target.hash.substring(1)}']`).cloneNode(true); - if (content instanceof HTMLElement) remove(content.querySelector(".reversefootnote")); - installContainer(ev.target); - void new Footnote(fragFromContents(content), ev.target); - }); + /** @param {Node} n */ + function fragFromContents(n) { + const frag = document.createDocumentFragment(); + n.childNodes.forEach((ch) => frag.appendChild(ch)); + return frag; + } + + /** @param {HTMLAnchorElement} a */ + function installContainer(a) { + if (!a.parentElement.matches(`.${CONTAINER_CLS}`)) { + const container = newEl("div", CONTAINER_CLS); + a.parentElement.insertBefore(container, a); + container.appendChild(a); + } + } + + document.addEventListener("click", (ev) => { + if (!(ev.target && ev.target instanceof HTMLAnchorElement)) return; + if (!ev.target.matches(".footnote")) return; + ev.preventDefault(); + + const content = document.querySelector(`[id='${ev.target.hash.substring(1)}']`).cloneNode(true); + if (content instanceof HTMLElement) { + remove(content.querySelector(".reversefootnote")); + } + installContainer(ev.target); + void new Footnote(fragFromContents(content), ev.target); + }); +}());