mirror of
https://github.com/Eggbertx/gochan.git
synced 2025-08-05 12:56:22 -07:00
263 lines
7.1 KiB
JavaScript
Executable file
263 lines
7.1 KiB
JavaScript
Executable file
/**
|
|
* @typedef { import("./types/gochan").BoardThread } BoardThread
|
|
* @typedef { import("./types/gochan").ThreadPost } ThreadPost
|
|
*/
|
|
|
|
|
|
import $ from "jquery";
|
|
|
|
import { alertLightbox } from "./dom/lightbox";
|
|
import { getBooleanStorageVal, getNumberStorageVal } from "./storage";
|
|
import { currentThread, getPageThread, insideOP } from "./postinfo";
|
|
import { addPostDropdown } from "./dom/postdropdown";
|
|
import { createPostElement } from "./dom/postelement";
|
|
import { getThreadJSON } from "./api/threads";
|
|
import { openQR } from "./dom/qr";
|
|
|
|
let doClickPreview = false;
|
|
let doHoverPreview = false;
|
|
let $hoverPreview = null;
|
|
let threadWatcherInterval = 0;
|
|
|
|
const videoTestRE = /\.(mp4)|(webm)$/;
|
|
const postrefRE = /\/([^\s/]+)\/res\/(\d+)\.html(#(\d+))?/;
|
|
|
|
// data retrieved from /<board>/res/<thread>.json
|
|
/** @type {BoardThread} */
|
|
let currentThreadJSON = {
|
|
posts: []
|
|
};
|
|
|
|
export function getUploadPostID(upload, container) {
|
|
// if container, upload is div.upload-container
|
|
// otherwise it's img or video
|
|
let jqu = container? $(upload) : $(upload).parent();
|
|
return insideOP(jqu) ? jqu.siblings().eq(4).text() : jqu.siblings().eq(3).text();
|
|
}
|
|
|
|
export function updateThreadJSON() {
|
|
let thread = currentThread();
|
|
if(thread.thread === 0) return; // not in a thread
|
|
return getThreadJSON(thread.thread, thread.board).then((json) => {
|
|
if(!(json.posts instanceof Array) || json.posts.length === 0)
|
|
return;
|
|
currentThreadJSON = json;
|
|
});
|
|
}
|
|
|
|
function updateThreadHTML() {
|
|
let thread = currentThread();
|
|
if(thread.thread === 0) return; // not in a thread
|
|
let numAdded = 0;
|
|
for(const post of currentThreadJSON.posts) {
|
|
let selector = "";
|
|
if(post.resto === 0)
|
|
selector += `div#${post.no}.thread`;
|
|
else
|
|
selector += `a#${post.no}.anchor`;
|
|
let elementExists = $(selector).length > 0;
|
|
if(elementExists)
|
|
continue; // TODO: check for edits
|
|
|
|
let $post = createPostElement(post, thread.board, "reply");
|
|
let $replyContainer = $("<div/>").prop({
|
|
id: `replycontainer${post.no}`,
|
|
class: "reply-container"
|
|
}).append($post);
|
|
$replyContainer.appendTo(`div#${post.resto}.thread`);
|
|
addPostDropdown($post);
|
|
prepareThumbnails($post);
|
|
initPostPreviews($post);
|
|
numAdded++;
|
|
}
|
|
if(numAdded === 0) return;
|
|
}
|
|
|
|
export function updateThread() {
|
|
return updateThreadJSON().then(updateThreadHTML);
|
|
}
|
|
|
|
function createPostPreview(e, $post, inline = true) {
|
|
let $preview = $post.clone();
|
|
if(inline) $preview = addPostDropdown($post.clone());
|
|
$preview
|
|
.prop({class: "inlinepostprev"})
|
|
.find("div.inlinepostprev").remove()
|
|
.find("a.postref").on("click", expandPost);
|
|
if(inline) {
|
|
$preview.insertAfter(e.target);
|
|
}
|
|
initPostPreviews($preview);
|
|
return $preview;
|
|
}
|
|
|
|
function previewMoveHandler(e) {
|
|
if($hoverPreview === null) return;
|
|
$hoverPreview.css({position: "absolute"}).offset({
|
|
top: e.pageY + 8,
|
|
left: e.pageX + 8
|
|
});
|
|
}
|
|
|
|
function expandPost(e) {
|
|
e.preventDefault();
|
|
if($hoverPreview !== null) $hoverPreview.remove();
|
|
let $next = $(e.target).next();
|
|
if($next.prop("class") == "inlinepostprev" && e.type == "click") {
|
|
// inline preview is already opened, close it
|
|
$next.remove();
|
|
return;
|
|
}
|
|
let href = e.target.href;
|
|
let hrefArr = postrefRE.exec(href);
|
|
if(hrefArr === null) return; // not actually a link to a post, abort
|
|
let postID = hrefArr[4]?hrefArr[4]:hrefArr[2];
|
|
|
|
let $post = $(`div#op${postID}, div#reply${postID}`).first();
|
|
if($post.length > 0) {
|
|
let $preview = createPostPreview(e, $post, e.type == "click");
|
|
if(e.type == "mouseenter") {
|
|
$hoverPreview = $preview.insertAfter(e.target);
|
|
$(document.body).on("mousemove", previewMoveHandler);
|
|
}
|
|
return;
|
|
}
|
|
if(e.type == "click") {
|
|
$.get(href, data => {
|
|
$post = $(data).find(`div#op${postID}, div#reply${postID}`).first();
|
|
if($post.length < 1) return; // post not on this page.
|
|
createPostPreview(e, $post, true);
|
|
}).catch((t, u, v) => {
|
|
alertLightbox(v, "Error");
|
|
return;
|
|
});
|
|
}
|
|
}
|
|
|
|
export function initPostPreviews($post = null) {
|
|
if(getPageThread().board == "" && $post === null) return;
|
|
doClickPreview = getBooleanStorageVal("enablepostclick", true);
|
|
doHoverPreview = getBooleanStorageVal("enableposthover", false);
|
|
let $refs = null;
|
|
$refs = $post === null ? $("a.postref") : $post.find("a.postref");
|
|
|
|
if(doClickPreview) {
|
|
$refs.on("click", expandPost);
|
|
} else {
|
|
$refs.off("click", expandPost);
|
|
}
|
|
|
|
if(doHoverPreview) {
|
|
$refs.on("mouseenter", expandPost).on("mouseleave", () => {
|
|
if($hoverPreview !== null) $hoverPreview.remove();
|
|
$hoverPreview = null;
|
|
$(document.body).off("mousemove", previewMoveHandler);
|
|
});
|
|
} else {
|
|
$refs.off("mouseenter").off("mouseleave").off("mousemove");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sets thumbnails to expand when clicked. If a parent is provided, prepareThumbnails will only
|
|
* be applied to that parent
|
|
* @param {JQuery<HTMLElement>} $post the post (if set) to prepare the thumbnails for
|
|
*/
|
|
export function prepareThumbnails($parent = null) {
|
|
let $container = $parent === null ? $("a.upload-container") : $parent.find("a")
|
|
$container.on("click", function(e) {
|
|
e.preventDefault();
|
|
let $a = $(this);
|
|
let thumb = $a.find("img.upload");
|
|
let thumbURL = thumb.attr("src");
|
|
let uploadURL = thumb.attr("alt");
|
|
thumb.removeAttr("width").removeAttr("height");
|
|
|
|
var fileInfoElement = $a.prevAll(".file-info:first");
|
|
|
|
if(videoTestRE.test(thumbURL + uploadURL)) {
|
|
// Upload is a video
|
|
thumb.hide();
|
|
var video = $("<video />")
|
|
.prop({
|
|
src: uploadURL,
|
|
autoplay: true,
|
|
controls: true,
|
|
class: "upload",
|
|
loop: true
|
|
}).insertAfter(fileInfoElement);
|
|
|
|
fileInfoElement.append($("<a />")
|
|
.prop("href", "javascript:;")
|
|
.on("click", function() {
|
|
video.remove();
|
|
thumb.show();
|
|
this.remove();
|
|
thumb.prop({
|
|
src: thumbURL,
|
|
alt: uploadURL
|
|
});
|
|
}).css({
|
|
"padding-left": "8px"
|
|
}).html("[Close]<br />"));
|
|
} else {
|
|
// upload is an image
|
|
thumb.attr({
|
|
src: uploadURL,
|
|
alt: thumbURL
|
|
});
|
|
}
|
|
return false;
|
|
});
|
|
}
|
|
|
|
function selectedText() {
|
|
if(!window.getSelection) return "";
|
|
return window.getSelection().toString();
|
|
}
|
|
|
|
export function quote(no) {
|
|
if(getBooleanStorageVal("useqr", true)) {
|
|
openQR();
|
|
}
|
|
let msgboxID = "postmsg";
|
|
|
|
let msgbox = document.getElementById("qr" + msgboxID);
|
|
if(msgbox === null)
|
|
msgbox = document.getElementById(msgboxID);
|
|
let selected = selectedText();
|
|
let lines = selected.split("\n");
|
|
|
|
if(selected !== "") {
|
|
for(let l = 0; l < lines.length; l++) {
|
|
lines[l] = ">" + lines[l];
|
|
}
|
|
}
|
|
let cursor = (msgbox.selectionStart !== undefined)?msgbox.selectionStart:msgbox.value.length;
|
|
let quoted = lines.join("\n");
|
|
if(quoted != "") quoted += "\n";
|
|
msgbox.value = msgbox.value.slice(0, cursor) + `>>${no}\n` +
|
|
quoted +
|
|
msgbox.value.slice(cursor);
|
|
|
|
if(msgbox.id == "postmsg")
|
|
window.scroll(0,msgbox.offsetTop - 48);
|
|
msgbox.focus();
|
|
}
|
|
window.quote = quote;
|
|
|
|
export function stopThreadWatcher() {
|
|
clearInterval(threadWatcherInterval);
|
|
}
|
|
|
|
export function resetThreadWatcherInterval() {
|
|
stopThreadWatcher();
|
|
threadWatcherInterval = setInterval(updateThread, getNumberStorageVal("watcherseconds", 10) * 1000);
|
|
}
|
|
|
|
$(() => {
|
|
let pageThread = getPageThread();
|
|
if(pageThread.op >= 1) {
|
|
resetThreadWatcherInterval();
|
|
}
|
|
});
|