delete js files
This commit is contained in:
@@ -1,39 +0,0 @@
|
|||||||
{
|
|
||||||
let ta = document.getElementById("babycode-content");
|
|
||||||
|
|
||||||
ta.addEventListener("keydown", (e) => {
|
|
||||||
if(e.key === "Enter" && e.ctrlKey) {
|
|
||||||
if (inThread()) {
|
|
||||||
localStorage.removeItem(window.location.pathname);
|
|
||||||
}
|
|
||||||
e.target.form?.submit();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const inThread = () => {
|
|
||||||
const scheme = window.location.pathname.split("/");
|
|
||||||
return scheme[1] === "threads" && scheme[2] !== "create";
|
|
||||||
}
|
|
||||||
|
|
||||||
ta.addEventListener("input", () => {
|
|
||||||
if (!inThread()) return;
|
|
||||||
|
|
||||||
localStorage.setItem(window.location.pathname, ta.value);
|
|
||||||
})
|
|
||||||
|
|
||||||
if (inThread()) {
|
|
||||||
const form = ta.closest('.post-edit-form');
|
|
||||||
if (form){
|
|
||||||
form.addEventListener("submit", () => {
|
|
||||||
localStorage.removeItem(window.location.pathname);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", () => {
|
|
||||||
if (!inThread()) return;
|
|
||||||
const prevContent = localStorage.getItem(window.location.pathname);
|
|
||||||
if (!prevContent) return;
|
|
||||||
ta.value = prevContent;
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,543 +0,0 @@
|
|||||||
const bookmarkMenuHrefTemplate = '/hyperapi/bookmarks-dropdown';
|
|
||||||
const badgeEditorEndpoint = '/hyperapi/badge-editor';
|
|
||||||
const previewEndpoint = '/api/babycode-preview';
|
|
||||||
const userEndpoint = '/api/current-user';
|
|
||||||
|
|
||||||
const delay = ms => {return new Promise(resolve => setTimeout(resolve, ms))}
|
|
||||||
|
|
||||||
export default class {
|
|
||||||
async showBookmarkMenu(ev, el) {
|
|
||||||
if ((ev.sender.dataset.bookmarkId === el.prop('bookmarkId')) && el.childElementCount === 0) {
|
|
||||||
const searchParams = new URLSearchParams({
|
|
||||||
'id': ev.sender.dataset.conceptId,
|
|
||||||
'require_reload': el.dataset.requireReload,
|
|
||||||
});
|
|
||||||
const bookmarkMenuHref = `${bookmarkMenuHrefTemplate}/${ev.sender.dataset.bookmarkType}?${searchParams}`;
|
|
||||||
const res = await this.api.getHTML(bookmarkMenuHref);
|
|
||||||
if (res.error) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const frag = res.value;
|
|
||||||
el.appendChild(frag);
|
|
||||||
const menu = el.childNodes[0];
|
|
||||||
menu.showPopover();
|
|
||||||
|
|
||||||
const bRect = el.getBoundingClientRect();
|
|
||||||
const menuRect = menu.getBoundingClientRect();
|
|
||||||
const preferredLeft = bRect.right - menuRect.width;
|
|
||||||
const preferredRight = bRect.right;
|
|
||||||
const enoughSpace = preferredLeft >= 0;
|
|
||||||
const scrollY = window.scrollY || window.pageYOffset;
|
|
||||||
if (enoughSpace) {
|
|
||||||
menu.style.left = `${preferredLeft}px`;
|
|
||||||
} else {
|
|
||||||
menu.style.left = `${bRect.left}px`;
|
|
||||||
}
|
|
||||||
menu.style.top = `${bRect.bottom + scrollY}px`;
|
|
||||||
|
|
||||||
menu.addEventListener('beforetoggle', (e) => {
|
|
||||||
if (e.newState === 'closed') {
|
|
||||||
// if it's still in the tree, remove it
|
|
||||||
// the delay is required to make sure its removed instantly when
|
|
||||||
// clicking the button when the menu is open
|
|
||||||
setTimeout(() => {menu.remove()}, 100);
|
|
||||||
};
|
|
||||||
}, { once: true });
|
|
||||||
|
|
||||||
} else if (el.childElementCount > 0) {
|
|
||||||
el.removeChild(el.childNodes[0]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
selectBookmarkCollection(ev, el) {
|
|
||||||
const clicked = ev.sender;
|
|
||||||
|
|
||||||
if (ev.sender === el) {
|
|
||||||
if (clicked.classList.contains('selected')) {
|
|
||||||
clicked.classList.remove('selected');
|
|
||||||
} else {
|
|
||||||
clicked.classList.add('selected');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
el.classList.remove('selected');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async saveBookmarks(ev, el) {
|
|
||||||
const bookmarkHref = el.prop('bookmarkEndpoint');
|
|
||||||
const collection = el.querySelector('.bookmark-dropdown-item.selected');
|
|
||||||
let data = {};
|
|
||||||
if (collection) {
|
|
||||||
data['operation'] = 'move';
|
|
||||||
data['collection_id'] = collection.dataset.collectionId;
|
|
||||||
data['memo'] = el.querySelector('.bookmark-memo-input').value;
|
|
||||||
} else {
|
|
||||||
data['operation'] = 'remove';
|
|
||||||
data['collection_id'] = el.prop('originallyContainedIn');
|
|
||||||
}
|
|
||||||
|
|
||||||
const options = {
|
|
||||||
method: 'POST',
|
|
||||||
body: JSON.stringify(data),
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
const requireReload = el.propToInt('requireReload') !== 0;
|
|
||||||
el.remove();
|
|
||||||
await fetch(bookmarkHref, options);
|
|
||||||
if (requireReload) {
|
|
||||||
window.location.reload();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async copyCode(ev, el) {
|
|
||||||
if (!el.isSender) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await navigator.clipboard.writeText(el.value);
|
|
||||||
el.textContent = 'Copied!'
|
|
||||||
await delay(1000);
|
|
||||||
el.textContent = 'Copy';
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleAccordion(ev, el) {
|
|
||||||
const accordion = el;
|
|
||||||
const header = accordion.querySelector('.accordion-header');
|
|
||||||
if (!header.contains(ev.sender)){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const btn = ev.sender;
|
|
||||||
const content = el.querySelector('.accordion-content');
|
|
||||||
// these are all meant to be in sync
|
|
||||||
accordion.classList.toggle('hidden');
|
|
||||||
content.classList.toggle('hidden');
|
|
||||||
btn.textContent = accordion.classList.contains('hidden') ? '+' : '-';
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleTab(ev, el) {
|
|
||||||
const tabButtonsContainer = el.querySelector('.tab-buttons');
|
|
||||||
if (!el.contains(ev.sender)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ev.sender.classList.contains('active')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const targetId = ev.sender.prop('targetId');
|
|
||||||
const contents = el.querySelectorAll('.tab-content');
|
|
||||||
for (let content of contents) {
|
|
||||||
if (content.id === targetId) {
|
|
||||||
content.classList.add('active');
|
|
||||||
} else {
|
|
||||||
content.classList.remove('active');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (let button of tabButtonsContainer.children) {
|
|
||||||
if (button.dataset.targetId === targetId) {
|
|
||||||
button.classList.add('active');
|
|
||||||
} else {
|
|
||||||
button.classList.remove('active');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#previousMarkup = null;
|
|
||||||
async babycodePreview(ev, el) {
|
|
||||||
if (ev.sender.classList.contains('active')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const previewErrorsContainer = el.querySelector('#babycode-preview-errors-container');
|
|
||||||
const previewContainer = el.querySelector('#babycode-preview-container');
|
|
||||||
const ta = document.getElementById('babycode-content');
|
|
||||||
const markup = ta.value.trim();
|
|
||||||
if (markup === '') {
|
|
||||||
previewErrorsContainer.textContent = 'Type something!';
|
|
||||||
previewContainer.textContent = '';
|
|
||||||
this.#previousMarkup = '';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (markup === this.#previousMarkup) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const bannedTags = JSON.parse(document.getElementById('babycode-banned-tags').value);
|
|
||||||
this.#previousMarkup = markup;
|
|
||||||
|
|
||||||
const res = await this.api.getJSON(previewEndpoint, [], {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
markup: markup,
|
|
||||||
banned_tags: bannedTags,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
if (res.error) {
|
|
||||||
switch (res.error.status) {
|
|
||||||
case 429:
|
|
||||||
previewErrorsContainer.textContent = '(Old preview, try again in a few seconds.)'
|
|
||||||
this.#previousMarkup = '';
|
|
||||||
break;
|
|
||||||
case 400:
|
|
||||||
previewErrorsContainer.textContent = '(Request got malformed.)'
|
|
||||||
break;
|
|
||||||
case 401:
|
|
||||||
previewErrorsContainer.textContent = '(You are not logged in.)'
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
previewErrorsContainer.textContent = '(Error. Check console.)'
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
previewErrorsContainer.textContent = '';
|
|
||||||
previewContainer.innerHTML = res.value.html;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
insertBabycodeTag(ev, el) {
|
|
||||||
const tagStart = ev.sender.prop('tag');
|
|
||||||
const breakLine = 'breakLine' in ev.sender.dataset;
|
|
||||||
const prefill = 'prefill' in ev.sender.dataset ? ev.sender.dataset.prefill : '';
|
|
||||||
|
|
||||||
const hasAttr = tagStart[tagStart.length - 1] === '=';
|
|
||||||
let tagEnd = tagStart;
|
|
||||||
let tagInsertStart = `[${tagStart}]${breakLine ? '\n' : ''}`;
|
|
||||||
if (hasAttr) {
|
|
||||||
tagEnd = tagEnd.slice(0, -1);
|
|
||||||
}
|
|
||||||
const tagInsertEnd = `${breakLine ? '\n' : ''}[/${tagEnd}]`;
|
|
||||||
const hasSelection = el.selectionStart !== el.selectionEnd;
|
|
||||||
const text = el.value;
|
|
||||||
|
|
||||||
if (hasSelection) {
|
|
||||||
const realStart = Math.min(el.selectionStart, el.selectionEnd);
|
|
||||||
const realEnd = Math.max(el.selectionStart, el.selectionEnd);
|
|
||||||
const selectionLength = realEnd - realStart;
|
|
||||||
|
|
||||||
const strStart = text.slice(0, realStart);
|
|
||||||
const strEnd = text.substring(realEnd);
|
|
||||||
const frag = `${tagInsertStart}${text.slice(realStart, realEnd)}${tagInsertEnd}`;
|
|
||||||
const reconst = `${strStart}${frag}${strEnd}`;
|
|
||||||
el.value = reconst;
|
|
||||||
if (!hasAttr) {
|
|
||||||
el.setSelectionRange(realStart + tagInsertStart.length, realStart + tagInsertEnd.length + selectionLength - 1);
|
|
||||||
} else {
|
|
||||||
const attrCursor = realStart + tagInsertEnd.length - (1 + (breakLine ? 1 : 0))
|
|
||||||
el.setSelectionRange(attrCursor, attrCursor); // cursor on attr
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (hasAttr) {
|
|
||||||
tagInsertStart += prefill;
|
|
||||||
}
|
|
||||||
const cursor = el.selectionStart;
|
|
||||||
const strStart = text.slice(0, cursor);
|
|
||||||
const strEnd = text.substr(cursor);
|
|
||||||
|
|
||||||
let newCursor = strStart.length + tagInsertStart.length;
|
|
||||||
if (hasAttr) {
|
|
||||||
newCursor = cursor + tagInsertStart.length - prefill.length - (1 + (breakLine ? 1 : 0)) //cursor on attr
|
|
||||||
}
|
|
||||||
const reconst = `${strStart}${tagInsertStart}${tagInsertEnd}${strEnd}`;
|
|
||||||
el.value = reconst;
|
|
||||||
el.setSelectionRange(newCursor, newCursor);
|
|
||||||
}
|
|
||||||
el.focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
addQuote(ev, el) {
|
|
||||||
el.value += ev.sender.value;
|
|
||||||
el.scrollIntoView();
|
|
||||||
el.focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
convertTimestamps(ev, el) {
|
|
||||||
const timestamp = el.propToInt('utc');
|
|
||||||
if (!isNaN(timestamp)) {
|
|
||||||
const date = new Date(timestamp * 1000);
|
|
||||||
el.textContent = date.toLocaleString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#currentUsername = undefined;
|
|
||||||
async highlightMentions(ev, el) {
|
|
||||||
if (this.#currentUsername === undefined) {
|
|
||||||
const userInfo = await this.api.getJSON(userEndpoint);
|
|
||||||
if (!userInfo.value) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.#currentUsername = userInfo.value.user.username;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (el.prop('username') === this.#currentUsername) {
|
|
||||||
el.classList.add('me');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class BadgeEditorForm {
|
|
||||||
#badgeTemplate = undefined;
|
|
||||||
async loadBadgeEditor(ev, el) {
|
|
||||||
const badges = await this.api.getHTML(badgeEditorEndpoint);
|
|
||||||
if (!badges.value) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (this.#badgeTemplate === undefined){
|
|
||||||
this.#badgeTemplate = document.getElementById('badge-editor-template').content.firstElementChild.outerHTML;
|
|
||||||
}
|
|
||||||
el.replaceChildren();
|
|
||||||
const addButton = `<button data-disable-if-max="1" data-receive="updateBadgeCount" DISABLE_IF_MAX type="button" data-send="addBadge">Add badge</button>`;
|
|
||||||
const submitButton = `<input data-receive="updateBadgeCount" type="submit" value="Save badges">`;
|
|
||||||
const controls = `<span>${addButton} ${submitButton} <span data-count="1" data-receive="updateBadgeCount">BADGECOUNT/10</span></span>`
|
|
||||||
const badgeCount = badges.value.querySelectorAll('.settings-badge-container').length;
|
|
||||||
const subs = [
|
|
||||||
['BADGECOUNT', badgeCount],
|
|
||||||
['DISABLE_IF_MAX', badgeCount === 10 ? 'disabled' : ''],
|
|
||||||
];
|
|
||||||
el.appendChild(this.api.makeHTML(controls, subs));
|
|
||||||
|
|
||||||
const listTemplate = document.getElementById('badges-list-template').content.firstElementChild.outerHTML;
|
|
||||||
const list = this.api.makeHTML(listTemplate).firstElementChild;
|
|
||||||
list.appendChild(badges.value);
|
|
||||||
el.appendChild(list);
|
|
||||||
}
|
|
||||||
|
|
||||||
addBadge(ev, el) {
|
|
||||||
if (this.#badgeTemplate === undefined) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const badge = this.api.makeHTML(this.#badgeTemplate).firstElementChild;
|
|
||||||
el.appendChild(badge);
|
|
||||||
this.api.localTrigger('updateBadgeCount');
|
|
||||||
}
|
|
||||||
|
|
||||||
deleteBadge(ev, el) {
|
|
||||||
if (!el.contains(ev.sender)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
el.remove();
|
|
||||||
this.api.localTrigger('updateBadgeCount');
|
|
||||||
}
|
|
||||||
|
|
||||||
updateBadgeCount(ev, el) {
|
|
||||||
const badgeCount = el.parentNode.parentNode.querySelectorAll('.settings-badge-container').length;
|
|
||||||
if (el.propToInt('disableIfMax') === 1) {
|
|
||||||
el.disabled = badgeCount === 10;
|
|
||||||
} else if (el.propToInt('count') === 1) {
|
|
||||||
el.textContent = `${badgeCount}/10`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
badgeEditorPrepareSubmit(ev, el) {
|
|
||||||
if (ev.type !== 'submit') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ev.preventDefault();
|
|
||||||
|
|
||||||
const badges = el.querySelectorAll('.settings-badge-container').length;
|
|
||||||
|
|
||||||
const noUploads = el.querySelectorAll('.settings-badge-file-picker.hidden input[type=file]');
|
|
||||||
noUploads.forEach(e => {
|
|
||||||
e.value = null;
|
|
||||||
})
|
|
||||||
el.submit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const validateBase64Img = dataURL => new Promise(resolve => {
|
|
||||||
const img = new Image();
|
|
||||||
img.onload = () => {
|
|
||||||
resolve(img.width === 88 && img.height === 31);
|
|
||||||
};
|
|
||||||
img.src = dataURL;
|
|
||||||
});
|
|
||||||
|
|
||||||
export class BadgeEditorBadge {
|
|
||||||
#badgeCustomImageData = null;
|
|
||||||
badgeUpdatePreview(ev, el) {
|
|
||||||
if (ev.type !== 'change') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// TODO: ev.sender doesn't have a bittyParent
|
|
||||||
const selectBittyParent = ev.sender.closest('bitty-7-0');
|
|
||||||
if (el.bittyParent !== selectBittyParent) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ev.value === 'custom') {
|
|
||||||
if (this.#badgeCustomImageData) {
|
|
||||||
el.src = this.#badgeCustomImageData;
|
|
||||||
} else {
|
|
||||||
el.removeAttribute('src');
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const option = ev.sender.selectedOptions[0];
|
|
||||||
el.src = option.dataset.filePath;
|
|
||||||
}
|
|
||||||
|
|
||||||
async badgeUpdatePreviewCustom(ev, el) {
|
|
||||||
if (ev.type !== 'change') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (el.bittyParent !== ev.sender.bittyParent) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const file = ev.target.files[0];
|
|
||||||
if (file.size >= 1000 * 500) {
|
|
||||||
this.api.localTrigger('badgeErrorSize');
|
|
||||||
this.#badgeCustomImageData = null;
|
|
||||||
el.removeAttribute('src');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const reader = new FileReader();
|
|
||||||
|
|
||||||
reader.onload = async e => {
|
|
||||||
const dimsValid = await validateBase64Img(e.target.result);
|
|
||||||
if (!dimsValid) {
|
|
||||||
this.api.localTrigger('badgeErrorDim');
|
|
||||||
this.#badgeCustomImageData = null;
|
|
||||||
el.removeAttribute('src');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.#badgeCustomImageData = e.target.result;
|
|
||||||
el.src = this.#badgeCustomImageData;
|
|
||||||
this.api.localTrigger('badgeHideErrors');
|
|
||||||
}
|
|
||||||
|
|
||||||
reader.readAsDataURL(file);
|
|
||||||
}
|
|
||||||
|
|
||||||
badgeToggleFilePicker(ev, el) {
|
|
||||||
if (ev.type !== 'change') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// TODO: ev.sender doesn't have a bittyParent
|
|
||||||
const selectBittyParent = ev.sender.closest('bitty-7-0');
|
|
||||||
if (el.bittyParent !== selectBittyParent) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const filePicker = el.querySelector('input[type=file]');
|
|
||||||
if (ev.value === 'custom') {
|
|
||||||
el.classList.remove('hidden');
|
|
||||||
if (filePicker.dataset.validity) {
|
|
||||||
filePicker.setCustomValidity(filePicker.dataset.validity);
|
|
||||||
}
|
|
||||||
filePicker.required = true;
|
|
||||||
} else {
|
|
||||||
el.classList.add('hidden');
|
|
||||||
filePicker.setCustomValidity('');
|
|
||||||
filePicker.required = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
openBadgeFilePicker(ev, el) {
|
|
||||||
// TODO: ev.sender doesn't have a bittyParent
|
|
||||||
if (ev.sender.parentNode !== el.parentNode) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
el.click();
|
|
||||||
}
|
|
||||||
|
|
||||||
badgeErrorSize(ev, el) {
|
|
||||||
const validity = "Image can't be over 500KB."
|
|
||||||
el.dataset.validity = validity;
|
|
||||||
el.setCustomValidity(validity);
|
|
||||||
el.reportValidity();
|
|
||||||
}
|
|
||||||
|
|
||||||
badgeErrorDim(ev, el) {
|
|
||||||
const validity = "Image must be exactly 88x31 pixels."
|
|
||||||
el.dataset.validity = validity;
|
|
||||||
el.setCustomValidity(validity);
|
|
||||||
el.reportValidity();
|
|
||||||
}
|
|
||||||
|
|
||||||
badgeHideErrors(ev, el) {
|
|
||||||
delete el.dataset.validity;
|
|
||||||
el.setCustomValidity('');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const getCollectionDataForEl = el => {
|
|
||||||
const nameInput = el.querySelector(".collection-name");
|
|
||||||
const collectionId = el.dataset.collectionId;
|
|
||||||
|
|
||||||
return {
|
|
||||||
id: collectionId,
|
|
||||||
name: nameInput.value,
|
|
||||||
is_new: !('collectionId' in el.dataset),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export class CollectionsEditor {
|
|
||||||
#collectionTemplate = undefined;
|
|
||||||
#collectionsData = [];
|
|
||||||
#removedCollections = [];
|
|
||||||
#valid = true;
|
|
||||||
|
|
||||||
addCollection(ev, el) {
|
|
||||||
if (this.#collectionTemplate === undefined) {
|
|
||||||
this.#collectionTemplate = document.getElementById('new-collection-template').content;
|
|
||||||
}
|
|
||||||
// interesting
|
|
||||||
const newCollection = this.api.makeHTML(this.#collectionTemplate.firstElementChild.outerHTML);
|
|
||||||
el.appendChild(newCollection);
|
|
||||||
}
|
|
||||||
|
|
||||||
deleteCollection(ev, el) {
|
|
||||||
if (!el.contains(ev.sender)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if ('collectionId' in el.dataset) {
|
|
||||||
this.#removedCollections.push(el.dataset.collectionId);
|
|
||||||
}
|
|
||||||
el.remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
async saveCollections(ev, el) {
|
|
||||||
this.#valid = true;
|
|
||||||
this.api.localTrigger('testValidity');
|
|
||||||
if (!this.#valid) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.#collectionsData = [];
|
|
||||||
this.api.localTrigger('getCollectionData');
|
|
||||||
|
|
||||||
const data = {
|
|
||||||
collections: this.#collectionsData,
|
|
||||||
removed_collections: this.#removedCollections,
|
|
||||||
};
|
|
||||||
|
|
||||||
const res = await this.api.getJSON(el.prop('submitHref'), [], {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify(data),
|
|
||||||
});
|
|
||||||
if (res.error) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
window.location.reload();
|
|
||||||
}
|
|
||||||
|
|
||||||
getCollectionData(ev, el) {
|
|
||||||
this.#collectionsData.push(getCollectionDataForEl(el));
|
|
||||||
}
|
|
||||||
|
|
||||||
testValidity(ev, el) {
|
|
||||||
const input = el.querySelector('input');
|
|
||||||
if (!input.validity.valid) {
|
|
||||||
input.reportValidity();
|
|
||||||
this.#valid = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,360 +0,0 @@
|
|||||||
{
|
|
||||||
const ta = document.getElementById("babycode-content");
|
|
||||||
|
|
||||||
function supportsPopover() {
|
|
||||||
return Object.hasOwn(HTMLElement.prototype, "popover");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (supportsPopover()){
|
|
||||||
let quotedPostContainer = null;
|
|
||||||
function isQuoteSelectionValid() {
|
|
||||||
const selection = document.getSelection();
|
|
||||||
|
|
||||||
if (!selection || selection.rangeCount === 0 || selection.isCollapsed) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const range = selection.getRangeAt(0);
|
|
||||||
const commonAncestor = range.commonAncestorContainer;
|
|
||||||
|
|
||||||
const ancestorElement = commonAncestor.nodeType === Node.TEXT_NODE
|
|
||||||
? commonAncestor.parentNode
|
|
||||||
: commonAncestor;
|
|
||||||
|
|
||||||
const container = ancestorElement.closest(".post-inner");
|
|
||||||
if (!container) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const success = container.contains(ancestorElement);
|
|
||||||
if (success) {
|
|
||||||
quotedPostContainer = container;
|
|
||||||
}
|
|
||||||
return success;
|
|
||||||
}
|
|
||||||
|
|
||||||
let quotePopover = null;
|
|
||||||
let isSelecting = false;
|
|
||||||
|
|
||||||
document.addEventListener("mousedown", () => {
|
|
||||||
isSelecting = true;
|
|
||||||
})
|
|
||||||
|
|
||||||
document.addEventListener("mouseup", () => {
|
|
||||||
isSelecting = false;
|
|
||||||
handlePossibleSelection();
|
|
||||||
})
|
|
||||||
|
|
||||||
document.addEventListener("keyup", (e) => {
|
|
||||||
if (e.shiftKey && (e.key.startsWith('Arrow') || e.key === 'Home' || e.key === 'End')) {
|
|
||||||
handlePossibleSelection();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
function handlePossibleSelection() {
|
|
||||||
setTimeout(() => {
|
|
||||||
const valid = isQuoteSelectionValid();
|
|
||||||
if (isSelecting || !valid) {
|
|
||||||
removeQuotePopover();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const selection = document.getSelection();
|
|
||||||
const selectionStr = selection.toString().trim();
|
|
||||||
if (selection.isCollapsed || selectionStr === "") {
|
|
||||||
removeQuotePopover();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
showQuotePopover();
|
|
||||||
}, 50)
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeQuotePopover() {
|
|
||||||
quotePopover?.hidePopover();
|
|
||||||
}
|
|
||||||
|
|
||||||
function createQuotePopover() {
|
|
||||||
quotePopover = document.createElement("div");
|
|
||||||
quotePopover.popover = "auto";
|
|
||||||
quotePopover.className = "quote-popover";
|
|
||||||
|
|
||||||
const quoteButton = document.createElement("button");
|
|
||||||
quoteButton.textContent = "Quote fragment"
|
|
||||||
quoteButton.className = "reduced"
|
|
||||||
quotePopover.appendChild(quoteButton);
|
|
||||||
document.body.appendChild(quotePopover);
|
|
||||||
return quoteButton;
|
|
||||||
}
|
|
||||||
|
|
||||||
function showQuotePopover() {
|
|
||||||
if (!quotePopover) {
|
|
||||||
const quoteButton = createQuotePopover();
|
|
||||||
quoteButton.addEventListener("click", () => {
|
|
||||||
console.log("Quoting:", document.getSelection().toString());
|
|
||||||
const postPermalink = quotedPostContainer.dataset.postPermalink;
|
|
||||||
const authorUsername = quotedPostContainer.dataset.authorUsername;
|
|
||||||
console.log(postPermalink, authorUsername);
|
|
||||||
if (ta.value.trim() !== "") {
|
|
||||||
ta.value += "\n"
|
|
||||||
}
|
|
||||||
ta.value += `@${authorUsername} [url=${postPermalink}]said:[/url]\n[quote]<:scissors:> ${document.getSelection().toString()} <:scissors:>[/quote]\n`;
|
|
||||||
ta.scrollIntoView()
|
|
||||||
ta.focus();
|
|
||||||
|
|
||||||
document.getSelection().empty();
|
|
||||||
removeQuotePopover();
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const range = document.getSelection().getRangeAt(0);
|
|
||||||
const rect = range.getBoundingClientRect();
|
|
||||||
const scrollY = window.scrollY || window.pageYOffset;
|
|
||||||
|
|
||||||
quotePopover.style.setProperty("top", `${rect.top + scrollY - 55}px`)
|
|
||||||
quotePopover.style.setProperty("left", `${rect.left + rect.width/2}px`)
|
|
||||||
|
|
||||||
if (!quotePopover.matches(':popover-open')) {
|
|
||||||
quotePopover.showPopover();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const deleteDialog = document.getElementById("delete-dialog");
|
|
||||||
const deleteDialogCloseButton = document.getElementById("post-delete-dialog-close");
|
|
||||||
let deletionTargetPostContainer;
|
|
||||||
|
|
||||||
function closeDeleteDialog() {
|
|
||||||
deletionTargetPostContainer.style.removeProperty("background-color");
|
|
||||||
deleteDialog.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
deleteDialogCloseButton.addEventListener("click", (e) => {
|
|
||||||
closeDeleteDialog();
|
|
||||||
})
|
|
||||||
deleteDialog.addEventListener("click", (e) => {
|
|
||||||
if (e.target === deleteDialog) {
|
|
||||||
closeDeleteDialog();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
for (let button of document.querySelectorAll(".post-delete-button")) {
|
|
||||||
button.addEventListener("click", (e) => {
|
|
||||||
deleteDialog.showModal();
|
|
||||||
const postId = button.value;
|
|
||||||
deletionTargetPostContainer = document.getElementById("post-" + postId).querySelector(".post-content-container");
|
|
||||||
deletionTargetPostContainer.style.setProperty("background-color", "#fff");
|
|
||||||
const form = document.getElementById("post-delete-form");
|
|
||||||
form.action = `/post/${postId}/delete`
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const threadEndpoint = document.getElementById("thread-subscribe-endpoint").value;
|
|
||||||
let now = Math.floor(new Date() / 1000);
|
|
||||||
function hideNotification() {
|
|
||||||
const notification = document.getElementById('new-post-notification');
|
|
||||||
notification.classList.add('hidden');
|
|
||||||
}
|
|
||||||
|
|
||||||
function showNewPostNotification(url) {
|
|
||||||
const notification = document.getElementById("new-post-notification");
|
|
||||||
|
|
||||||
notification.classList.remove("hidden");
|
|
||||||
|
|
||||||
document.getElementById("dismiss-new-post-button").onclick = () => {
|
|
||||||
now = Math.floor(new Date() / 1000);
|
|
||||||
hideNotification();
|
|
||||||
tryFetchUpdate();
|
|
||||||
}
|
|
||||||
|
|
||||||
document.getElementById("go-to-new-post-button").href = url;
|
|
||||||
|
|
||||||
document.getElementById("unsub-new-post-button").onclick = () => {
|
|
||||||
hideNotification();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function tryFetchUpdate() {
|
|
||||||
if (!threadEndpoint) return;
|
|
||||||
const body = JSON.stringify({'since': now});
|
|
||||||
fetch(threadEndpoint, {method: "POST", headers: {"Content-Type": "application/json"}, body: body})
|
|
||||||
.then(res => res.json())
|
|
||||||
.then(json => {
|
|
||||||
if (json.status === "none") {
|
|
||||||
setTimeout(tryFetchUpdate, 5000);
|
|
||||||
} else if (json.status === "new_post") {
|
|
||||||
showNewPostNotification(json.url);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(error => console.log(error))
|
|
||||||
}
|
|
||||||
tryFetchUpdate();
|
|
||||||
|
|
||||||
if (supportsPopover()){
|
|
||||||
const reactionEmoji = document.getElementById("allowed-reaction-emoji").value.split(" ");
|
|
||||||
let reactionPopover = null;
|
|
||||||
let reactionTargetPostId = null;
|
|
||||||
|
|
||||||
function tryAddReaction(emoji, postId = reactionTargetPostId) {
|
|
||||||
const body = JSON.stringify({
|
|
||||||
"emoji": emoji,
|
|
||||||
});
|
|
||||||
fetch(`/api/add-reaction/${postId}`, {method: "POST", headers: {"Content-Type": "application/json"}, body: body})
|
|
||||||
.then(res => res.json())
|
|
||||||
.then(json => {
|
|
||||||
if (json.status === "added") {
|
|
||||||
const post = document.getElementById(`post-${postId}`);
|
|
||||||
const spans = Array.from(post.querySelectorAll(".reaction-count")).filter((span) => {
|
|
||||||
return span.dataset.emoji === emoji
|
|
||||||
});
|
|
||||||
if (spans.length > 0) {
|
|
||||||
const currentValue = spans[0].textContent;
|
|
||||||
spans[0].textContent = `${parseInt(currentValue) + 1}`;
|
|
||||||
const button = spans[0].closest(".reaction-button");
|
|
||||||
button.classList.add("active");
|
|
||||||
} else {
|
|
||||||
const span = document.createElement("span");
|
|
||||||
span.classList = "reaction-container";
|
|
||||||
span.dataset.emoji = emoji;
|
|
||||||
|
|
||||||
const button = document.createElement("button");
|
|
||||||
button.type = "button";
|
|
||||||
button.className = "reduced reaction-button active";
|
|
||||||
|
|
||||||
button.addEventListener("click", () => {
|
|
||||||
tryAddReaction(emoji, postId);
|
|
||||||
})
|
|
||||||
|
|
||||||
const img = document.createElement("img");
|
|
||||||
img.src = `/static/emoji/${emoji}.png`;
|
|
||||||
|
|
||||||
button.textContent = " x";
|
|
||||||
|
|
||||||
const reactionCountSpan = document.createElement("span")
|
|
||||||
reactionCountSpan.className = "reaction-count"
|
|
||||||
reactionCountSpan.textContent = "1"
|
|
||||||
|
|
||||||
button.insertAdjacentElement("afterbegin", img);
|
|
||||||
button.appendChild(reactionCountSpan);
|
|
||||||
|
|
||||||
span.appendChild(button);
|
|
||||||
|
|
||||||
const post = document.getElementById(`post-${postId}`);
|
|
||||||
post.querySelector(".post-reactions").insertBefore(span, post.querySelector(".add-reaction-button"));
|
|
||||||
}
|
|
||||||
} else if (json.error_code === 409) {
|
|
||||||
console.log("reaction exists, gonna try and remove");
|
|
||||||
tryRemoveReaction(emoji, postId);
|
|
||||||
} else {
|
|
||||||
console.warn(json)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(error => console.error(error));
|
|
||||||
}
|
|
||||||
|
|
||||||
function tryRemoveReaction(emoji, postId = reactionTargetPostId) {
|
|
||||||
const body = JSON.stringify({
|
|
||||||
"emoji": emoji,
|
|
||||||
});
|
|
||||||
|
|
||||||
fetch(`/api/remove-reaction/${postId}`, {method: "POST", headers: {"Content-Type": "application/json"}, body: body})
|
|
||||||
.then(res => res.json())
|
|
||||||
.then(json => {
|
|
||||||
if (json.status === "removed") {
|
|
||||||
const post = document.getElementById(`post-${postId}`);
|
|
||||||
const spans = Array.from(post.querySelectorAll(".reaction-container")).filter((span) => {
|
|
||||||
return span.dataset.emoji === emoji
|
|
||||||
});
|
|
||||||
if (spans.length > 0) {
|
|
||||||
const reactionCountSpan = spans[0].querySelector(".reaction-count");
|
|
||||||
const currentValue = parseInt(reactionCountSpan.textContent);
|
|
||||||
if (currentValue - 1 === 0) {
|
|
||||||
spans[0].remove();
|
|
||||||
} else {
|
|
||||||
reactionCountSpan.textContent = `${parseInt(currentValue) - 1}`;
|
|
||||||
const button = reactionCountSpan.closest(".reaction-button");
|
|
||||||
button.classList.remove("active");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.warn(json)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(error => console.error(error));
|
|
||||||
}
|
|
||||||
|
|
||||||
function createReactionPopover() {
|
|
||||||
reactionPopover = document.createElement("div");
|
|
||||||
reactionPopover.className = "reaction-popover";
|
|
||||||
reactionPopover.popover = "auto";
|
|
||||||
|
|
||||||
const inner = document.createElement("div");
|
|
||||||
inner.className = "reaction-popover-inner";
|
|
||||||
|
|
||||||
reactionPopover.appendChild(inner);
|
|
||||||
|
|
||||||
for (let emoji of reactionEmoji) {
|
|
||||||
const img = document.createElement("img");
|
|
||||||
img.src = `/static/emoji/${emoji}.png`;
|
|
||||||
|
|
||||||
const button = document.createElement("button");
|
|
||||||
button.type = "button";
|
|
||||||
button.className = "reduced";
|
|
||||||
button.appendChild(img);
|
|
||||||
button.addEventListener("click", () => {
|
|
||||||
tryAddReaction(emoji);
|
|
||||||
})
|
|
||||||
|
|
||||||
button.dataset.emojiName = emoji;
|
|
||||||
|
|
||||||
inner.appendChild(button);
|
|
||||||
}
|
|
||||||
|
|
||||||
reactionPopover.addEventListener("beforetoggle", (e) => {
|
|
||||||
if (e.newState === "closed") {
|
|
||||||
reactionTargetPostId = null;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
document.body.appendChild(reactionPopover);
|
|
||||||
}
|
|
||||||
|
|
||||||
function showReactionPopover() {
|
|
||||||
if (!reactionPopover) {
|
|
||||||
createReactionPopover();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!reactionPopover.matches(':popover-open')) {
|
|
||||||
reactionPopover.showPopover();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let button of document.querySelectorAll(".add-reaction-button")) {
|
|
||||||
button.addEventListener("click", (e) => {
|
|
||||||
showReactionPopover();
|
|
||||||
reactionTargetPostId = e.target.dataset.postId;
|
|
||||||
|
|
||||||
const rect = e.target.getBoundingClientRect();
|
|
||||||
const popoverRect = reactionPopover.getBoundingClientRect();
|
|
||||||
const scrollY = window.scrollY || window.pageYOffset;
|
|
||||||
|
|
||||||
reactionPopover.style.setProperty("top", `${rect.top + scrollY + rect.height}px`)
|
|
||||||
reactionPopover.style.setProperty("left", `${rect.left + rect.width/2 - popoverRect.width/2}px`)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let button of document.querySelectorAll(".reaction-button")) {
|
|
||||||
button.addEventListener("click", () => {
|
|
||||||
const reactionContainer = button.closest(".reaction-container")
|
|
||||||
const emoji = reactionContainer.dataset.emoji;
|
|
||||||
const postId = reactionContainer.dataset.postId;
|
|
||||||
console.log(reactionContainer);
|
|
||||||
tryAddReaction(emoji, postId);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
for (let button of document.querySelectorAll(".add-reaction-button")) {
|
|
||||||
button.disabled = true;
|
|
||||||
button.title = "Enable JS to add reactions."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
{
|
|
||||||
const deleteDialog = document.getElementById("delete-dialog");
|
|
||||||
const deleteDialogOpenButton = document.getElementById("topic-delete-dialog-open");
|
|
||||||
deleteDialogOpenButton.addEventListener("click", (e) => {
|
|
||||||
deleteDialog.showModal();
|
|
||||||
});
|
|
||||||
const deleteDialogCloseButton = document.getElementById("topic-delete-dialog-close");
|
|
||||||
deleteDialogCloseButton.addEventListener("click", (e) => {
|
|
||||||
deleteDialog.close();
|
|
||||||
})
|
|
||||||
deleteDialog.addEventListener("click", (e) => {
|
|
||||||
if (e.target === deleteDialog) {
|
|
||||||
deleteDialog.close();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,235 +0,0 @@
|
|||||||
function openLightbox(post, idx) {
|
|
||||||
lightboxCurrentPost = post;
|
|
||||||
lightboxCurrentIdx = idx;
|
|
||||||
lightboxObj.img.src = lightboxImages.get(post)[idx].src;
|
|
||||||
lightboxObj.openOriginalAnchor.href = lightboxImages.get(post)[idx].src
|
|
||||||
lightboxObj.prevButton.disabled = lightboxImages.get(post).length === 1
|
|
||||||
lightboxObj.nextButton.disabled = lightboxImages.get(post).length === 1
|
|
||||||
lightboxObj.imageCount.textContent = `Image ${idx + 1} of ${lightboxImages.get(post).length}`
|
|
||||||
|
|
||||||
if (!lightboxObj.dialog.open) {
|
|
||||||
lightboxObj.dialog.showModal();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const modulo = (n, m) => ((n % m) + m) % m
|
|
||||||
|
|
||||||
function lightboxNext() {
|
|
||||||
const l = lightboxImages.get(lightboxCurrentPost).length;
|
|
||||||
const target = modulo(lightboxCurrentIdx + 1, l);
|
|
||||||
openLightbox(lightboxCurrentPost, target);
|
|
||||||
}
|
|
||||||
|
|
||||||
function lightboxPrev() {
|
|
||||||
const l = lightboxImages.get(lightboxCurrentPost).length;
|
|
||||||
const target = modulo(lightboxCurrentIdx - 1, l);
|
|
||||||
openLightbox(lightboxCurrentPost, target);
|
|
||||||
}
|
|
||||||
|
|
||||||
function constructLightbox() {
|
|
||||||
const dialog = document.createElement("dialog");
|
|
||||||
dialog.classList.add("lightbox-dialog");
|
|
||||||
dialog.addEventListener("click", (e) => {
|
|
||||||
if (e.target === dialog) {
|
|
||||||
dialog.close();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
const dialogInner = document.createElement("div");
|
|
||||||
dialogInner.classList.add("lightbox-inner");
|
|
||||||
dialog.appendChild(dialogInner);
|
|
||||||
const img = document.createElement("img");
|
|
||||||
img.classList.add("lightbox-image")
|
|
||||||
dialogInner.appendChild(img);
|
|
||||||
const openOriginalAnchor = document.createElement("a")
|
|
||||||
openOriginalAnchor.text = "Open original in new window"
|
|
||||||
openOriginalAnchor.target = "_blank"
|
|
||||||
openOriginalAnchor.rel = "noopener noreferrer nofollow"
|
|
||||||
dialogInner.appendChild(openOriginalAnchor);
|
|
||||||
|
|
||||||
const navSpan = document.createElement("span");
|
|
||||||
navSpan.classList.add("lightbox-nav");
|
|
||||||
const prevButton = document.createElement("button");
|
|
||||||
prevButton.type = "button";
|
|
||||||
prevButton.textContent = "Previous";
|
|
||||||
prevButton.addEventListener("click", lightboxPrev);
|
|
||||||
const nextButton = document.createElement("button");
|
|
||||||
nextButton.type = "button";
|
|
||||||
nextButton.textContent = "Next";
|
|
||||||
nextButton.addEventListener("click", lightboxNext);
|
|
||||||
const imageCount = document.createElement("span");
|
|
||||||
imageCount.textContent = "Image of ";
|
|
||||||
navSpan.appendChild(prevButton);
|
|
||||||
navSpan.appendChild(imageCount);
|
|
||||||
navSpan.appendChild(nextButton);
|
|
||||||
|
|
||||||
dialogInner.appendChild(navSpan);
|
|
||||||
return {
|
|
||||||
img: img,
|
|
||||||
dialog: dialog,
|
|
||||||
openOriginalAnchor: openOriginalAnchor,
|
|
||||||
prevButton: prevButton,
|
|
||||||
nextButton: nextButton,
|
|
||||||
imageCount: imageCount,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let lightboxImages = new Map(); //.post-inner : Array<Object>
|
|
||||||
let lightboxObj = null;
|
|
||||||
let lightboxCurrentPost = null;
|
|
||||||
let lightboxCurrentIdx = -1;
|
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", () => {
|
|
||||||
//lightboxes
|
|
||||||
lightboxObj = constructLightbox();
|
|
||||||
document.body.appendChild(lightboxObj.dialog);
|
|
||||||
|
|
||||||
function setImageMaxSize(img) {
|
|
||||||
const {
|
|
||||||
maxWidth: origMaxWidth,
|
|
||||||
maxHeight: origMaxHeight,
|
|
||||||
minWidth: origMinWidth,
|
|
||||||
minHeight: origMinHeight,
|
|
||||||
} = getComputedStyle(img);
|
|
||||||
if (img.naturalWidth < parseInt(origMinWidth)) {
|
|
||||||
img.style.minWidth = img.naturalWidth + "px";
|
|
||||||
}
|
|
||||||
if (img.naturalHeight < parseInt(origMinHeight)) {
|
|
||||||
img.style.minHeight = img.naturalHeight + "px";
|
|
||||||
}
|
|
||||||
if (img.naturalWidth < parseInt(origMaxWidth)) {
|
|
||||||
img.style.maxWidth = img.naturalWidth + "px";
|
|
||||||
}
|
|
||||||
if (img.naturalHeight < parseInt(origMaxHeight)) {
|
|
||||||
img.style.maxHeight = img.naturalHeight + "px";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const postImages = document.querySelectorAll(".post-inner img.post-image");
|
|
||||||
postImages.forEach(postImage => {
|
|
||||||
const belongingTo = postImage.closest(".post-inner");
|
|
||||||
const images = lightboxImages.get(belongingTo) ?? [];
|
|
||||||
images.push({
|
|
||||||
src: postImage.src,
|
|
||||||
alt: postImage.alt,
|
|
||||||
});
|
|
||||||
const idx = images.length - 1;
|
|
||||||
lightboxImages.set(belongingTo, images);
|
|
||||||
postImage.style.cursor = "pointer";
|
|
||||||
postImage.addEventListener("click", () => {
|
|
||||||
openLightbox(belongingTo, idx);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
const postAndSigImages = document.querySelectorAll("img.post-image");
|
|
||||||
postAndSigImages.forEach(image => {
|
|
||||||
if (image.complete) {
|
|
||||||
setImageMaxSize(image);
|
|
||||||
} else {
|
|
||||||
image.addEventListener("load", () => setImageMaxSize(image));
|
|
||||||
}
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
{
|
|
||||||
function isBefore(el1, el2) {
|
|
||||||
if (el2.parentNode === el1.parentNode) {
|
|
||||||
for (let cur = el1.previousSibling; cur; cur = cur.previousSibling) {
|
|
||||||
if (cur === el2) return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let draggedItem = null;
|
|
||||||
|
|
||||||
function sortableItemDragStart(e, item) {
|
|
||||||
const box = item.getBoundingClientRect();
|
|
||||||
const oX = e.clientX - box.left;
|
|
||||||
const oY = e.clientY - box.top;
|
|
||||||
draggedItem = item;
|
|
||||||
item.classList.add('dragged');
|
|
||||||
e.dataTransfer.setDragImage(item, oX, oY);
|
|
||||||
e.dataTransfer.effectAllowed = 'move';
|
|
||||||
}
|
|
||||||
|
|
||||||
function sortableItemDragEnd(e, item) {
|
|
||||||
draggedItem = null;
|
|
||||||
item.classList.remove('dragged');
|
|
||||||
}
|
|
||||||
|
|
||||||
function sortableItemDragOver(e, item) {
|
|
||||||
const target = e.target.closest('.sortable-item');
|
|
||||||
if (!target || target === draggedItem) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const inSameList = draggedItem.dataset.sortableListKey === target.dataset.sortableListKey;
|
|
||||||
if (!inSameList) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const targetList = draggedItem.closest('.sortable-list');
|
|
||||||
if (isBefore(draggedItem, target)) {
|
|
||||||
targetList.insertBefore(draggedItem, target);
|
|
||||||
} else {
|
|
||||||
targetList.insertBefore(draggedItem, target.nextSibling);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const listItemsHandled = new Map();
|
|
||||||
|
|
||||||
const getListItemsHandled = (list) => {
|
|
||||||
return listItemsHandled.get(list) || new Set();
|
|
||||||
}
|
|
||||||
|
|
||||||
function registerSortableList(list) {
|
|
||||||
list.querySelectorAll('li:not(.immovable)').forEach(item => {
|
|
||||||
const listItems = getListItemsHandled(list);
|
|
||||||
listItems.add(item);
|
|
||||||
listItemsHandled.set(list, listItems);
|
|
||||||
const dragger = item.querySelector('.dragger');
|
|
||||||
dragger.addEventListener('dragstart', e => {sortableItemDragStart(e, item)});
|
|
||||||
dragger.addEventListener('dragend', e => {sortableItemDragEnd(e, item)});
|
|
||||||
item.addEventListener('dragover', e => {sortableItemDragOver(e, item)});
|
|
||||||
});
|
|
||||||
|
|
||||||
const obs = new MutationObserver(records => {
|
|
||||||
for (const mutation of records) {
|
|
||||||
mutation.addedNodes.forEach(node => {
|
|
||||||
if (!(node instanceof HTMLElement)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!node.classList.contains('sortable-item')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const listItems = getListItemsHandled(list)
|
|
||||||
if (listItems.has(node)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const dragger = node.querySelector('.dragger');
|
|
||||||
dragger.addEventListener('dragstart', e => {sortableItemDragStart(e, node)});
|
|
||||||
dragger.addEventListener('dragend', e => {sortableItemDragEnd(e, node)});
|
|
||||||
node.addEventListener('dragover', e => {sortableItemDragOver(e, node)});
|
|
||||||
listItems.add(node);
|
|
||||||
listItemsHandled.set(list, listItems);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
obs.observe(list, {childList: true});
|
|
||||||
}
|
|
||||||
|
|
||||||
document.querySelectorAll('.sortable-list').forEach(registerSortableList);
|
|
||||||
|
|
||||||
listsObs = new MutationObserver(records => {
|
|
||||||
for (const mutation of records) {
|
|
||||||
mutation.addedNodes.forEach(node => {
|
|
||||||
if (!(node instanceof HTMLElement)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!node.classList.contains('sortable-list')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
registerSortableList(node);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
listsObs.observe(document.body, {childList: true, subtree: true});
|
|
||||||
}
|
|
||||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user