diff --git a/crates/treehouse/src/html/markdown.rs b/crates/treehouse/src/html/markdown.rs index 56f4e23..5f72213 100644 --- a/crates/treehouse/src/html/markdown.rs +++ b/crates/treehouse/src/html/markdown.rs @@ -492,8 +492,10 @@ where .copied(); if let Some(branch) = branch_id.map(|id| self.treehouse.tree.branch(id)) { - self.writer.write_str("")?; } self.writer.write_str("\"")?;")?; if branch_id.is_some() { self.writer.write_str("")?; diff --git a/static/css/main.css b/static/css/main.css index 63cbcc8..0469fd1 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -459,19 +459,12 @@ img[is="th-emoji"] { /* And also style emoji tooltips. */ -.emoji-wrapper { - --transition-duration: 0.2s; - - position: relative; -} - -.emoji-tooltip { +th-emoji-tooltip { display: flex; flex-direction: column; align-items: center; - position: absolute; - left: 50%; + position: fixed; transform: translateX(-50%) translateY(-10%) scale(0.8); width: max-content; z-index: 100; @@ -490,24 +483,24 @@ img[is="th-emoji"] { pointer-events: none; } -.emoji-wrapper:hover .emoji-tooltip { +th-emoji-tooltip.transitioned-in { opacity: 100%; filter: blur(0px); transform: translateX(-50%) scale(1.0); } -.emoji-tooltip img { +th-emoji-tooltip img { display: block; max-width: 64px; max-height: 64px; } -.emoji-tooltip p { +th-emoji-tooltip p { --recursive-wght: 550; color: var(--text-color); - font-size: 0.75em; + font-size: 0.9em; margin: 0; - padding-top: 4px; + padding-top: 6px; line-height: 1; } diff --git a/static/js/emoji.js b/static/js/emoji.js index 046b7bf..87a7d84 100644 --- a/static/js/emoji.js +++ b/static/js/emoji.js @@ -1,29 +1,118 @@ // Emoji zoom-in functionality. -class Emoji extends HTMLImageElement { - constructor() { +class EmojiTooltip extends HTMLElement { + constructor(emoji, { onClosed }) { super(); - this.wrapper = document.createElement("span"); - this.wrapper.className = "emoji-wrapper"; - this.replaceWith(this.wrapper); - this.wrapper.appendChild(this); + this.emoji = emoji; + this.onClosed = onClosed; + } - this.enlarged = new Image(); - this.enlarged.src = this.src; + connectedCallback() { + this.role = "tooltip"; - this.titleElement = document.createElement("p"); - this.titleElement.innerText = this.title; + this.image = new Image(); + this.image.src = this.emoji.src; - this.tooltip = document.createElement("div"); - this.tooltip.className = "emoji-tooltip"; - this.tooltip.appendChild(this.enlarged); - this.tooltip.appendChild(this.titleElement); + this.description = document.createElement("p"); + this.description.textContent = `${this.emoji.emojiName}`; - this.wrapper.appendChild(this.tooltip); + let emojiBoundingBox = this.emoji.getBoundingClientRect(); + this.style.left = `${emojiBoundingBox.left + emojiBoundingBox.width / 2}px`; + this.style.top = `calc(${emojiBoundingBox.top}px + 1.5em)`; - this.alt = this.title; + this.fullyOpaque = false; + this.addEventListener("transitionend", event => { + if (event.propertyName == "opacity") { + this.fullyOpaque = !this.fullyOpaque; + if (!this.fullyOpaque) { + this.onClosed(); + } + } + }); + // Timeout is zero because we just want to execute this later, to be definitely sure + // the transition plays out. + setTimeout(() => this.classList.add("transitioned-in"), 0); + + this.appendChild(this.image); + this.appendChild(this.description); + } + + close() { + this.classList.remove("transitioned-in"); + } +} + +customElements.define("th-emoji-tooltip", EmojiTooltip); + +let emojiTooltips = null; +addEventListener("wheel", event => emojiTooltips.closeTooltips(event)); + +class EmojiTooltips extends HTMLElement { + constructor() { + super(); + this.tooltips = new Set(); + this.abortController = new AbortController(); + + } + + connectedCallback() { + emojiTooltips = this; + } + + disconnectedCallback() { + this.abortController.abort(); + } + + openTooltip(emoji) { + let tooltip = new EmojiTooltip(emoji, { + onClosed: () => { + this.removeChild(tooltip); + this.tooltips.delete(tooltip); + }, + }); + + this.appendChild(tooltip); + this.tooltips.add(tooltip); + + return tooltip; + } + + closeTooltip(tooltip) { + tooltip.close(); + } + + closeTooltips() { + for (let tooltip of this.tooltips) { + console.log("close", this); + + tooltip.close(); + } + } +} + +customElements.define("th-emoji-tooltips", EmojiTooltips); + +class Emoji extends HTMLImageElement { + connectedCallback() { + this.emojiName = this.title; + + // title makes the browser add a tooltip. We replace browser tooltips with our own, + // so remove the title. this.title = ""; + + this.addEventListener("mouseenter", () => this.openTooltip()); + this.addEventListener("mouseleave", () => this.closeTooltip()); + this.addEventListener("scroll", () => this.closeTooltip()); + } + + openTooltip() { + this.tooltip = emojiTooltips.openTooltip(this); + } + + closeTooltip() { + emojiTooltips.closeTooltip(this.tooltip); + this.tooltip = null; } }