Files
czchan/web/ts/posts/quote/hover_quote.ts
sneedmaster ac9ea8f308 Overboard
2026-04-20 00:05:51 +02:00

164 lines
4.1 KiB
TypeScript

import { parseQuote } from "../../lib/util";
import { clickAction } from "../click_action";
import { expand } from "../expand";
import { addYous } from "../yous";
import { clickQuote } from "./click_quote";
import $ from "jquery";
const hoverQuote = (quotes: JQuery<HTMLElement>) => {
const cache: { [key: string]: string } = {};
let hovering = false;
let previewW = 0;
let previewH = 0;
quotes.on("mouseover", function (event) {
toggleHover($(this), event);
});
quotes.on("mouseout", function (event) {
toggleHover($(this), event);
});
quotes.on("click", function (event) {
toggleHover($(this), event);
});
quotes.on("mousemove", movePreview);
const toggleHover = (
quote: JQuery<HTMLElement>,
event: JQuery.MouseEventBase,
) => {
hovering = event.type === "mouseover";
if ($("#preview").length > 0 && !hovering) {
removePreview();
return;
}
const board =
quote.closest(".post, .catalog-tile").attr("data-board") || null;
const id = parseQuote(board, quote.text());
const existingPost = $(`#${id}.post`); // Catalog tiles don't count
const url = quote.attr("href");
console.log(id, existingPost[0]);
if (existingPost.length > 0 && isInViewport(existingPost)) {
existingPost.toggleClass("highlighted", hovering);
return;
}
if (existingPost.length > 0 && hovering) {
createPreview(existingPost.clone(), event.clientX, event.clientY);
return;
}
const cachedPost = $(cache[id]);
if (cachedPost.length > 0) {
createPreview(cachedPost, event.clientX, event.clientY);
return;
}
quote.css("cursor", "wait");
$.ajax({
url,
success: function (html) {
const posts = $(html).find(".post");
posts.each(function () {
const board = $(this).attr("data-board");
const id = $(this).attr("data-id");
cache[`${board}-${id}`] = this.outerHTML;
});
const post = $(cache[id]);
createPreview(post, event.clientX, event.clientY);
quote.css("cursor", "");
},
error: function () {
quote.css("cursor", "");
},
});
};
function movePreview(event: JQuery.MouseEventBase) {
positionPreview($("#preview"), event.clientX, event.clientY);
}
function createPreview(preview: JQuery<HTMLElement>, x: number, y: number) {
if (!hovering) {
return;
}
preview.attr("id", "preview");
preview.addClass("reply");
preview.removeClass("highlighted");
preview.css("position", "fixed");
const existing = $("#preview");
if (existing.length > 0) {
existing.replaceWith(preview);
} else {
preview.appendTo("body");
}
previewW = preview.outerWidth() || 0;
previewH = preview.outerHeight() || 0;
positionPreview(preview, x, y);
// Set up events
clickQuote(preview.find(".quote-link"));
hoverQuote(preview.find(".quote"));
clickAction(preview.find(".post-checkbox"));
expand(preview.find(".thumb-link"));
addYous(preview);
}
function removePreview() {
$("#preview").remove();
}
function positionPreview(preview: JQuery<HTMLElement>, x: number, y: number) {
const ww = $(window).width() || 0;
const wh = $(window).height() || 0;
preview.css("left", `${Math.min(x + 4, ww - previewW)}px`);
if (previewH + y < wh) {
preview.css("top", `${y + 4}px`);
preview.css("bottom", "");
} else {
preview.css("bottom", `${wh - y + 4}px`);
preview.css("top", "");
}
}
};
const isInViewport = function (element: JQuery<HTMLElement>) {
const offset = element.offset();
const width = element.outerWidth();
const height = element.outerHeight();
const viewportTop = $(window).scrollTop();
const windowHeight = $(window).height();
if (!offset) return false;
if (!width) return false;
if (!height) return false;
if (!viewportTop) return false;
if (!windowHeight) return false;
const elementTop = offset.top;
const elementBottom = elementTop + height;
const viewportBottom = viewportTop + windowHeight;
return elementBottom > viewportTop && elementTop < viewportBottom;
};
export { hoverQuote };