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;
}
}