move over a bunch of ui functionality to bitty

This commit is contained in:
2025-11-29 06:58:41 +03:00
parent 0bed6b58ae
commit bbe57d6e94
6 changed files with 182 additions and 249 deletions

View File

@@ -3,7 +3,6 @@
ta.addEventListener("keydown", (e) => {
if(e.key === "Enter" && e.ctrlKey) {
// console.log(e.target.form)
if (inThread()) {
localStorage.removeItem(window.location.pathname);
}
@@ -38,152 +37,4 @@
if (!prevContent) return;
ta.value = prevContent;
})
const buttonBold = document.getElementById("post-editor-bold");
const buttonItalics = document.getElementById("post-editor-italics");
const buttonStrike = document.getElementById("post-editor-strike");
const buttonUnderline = document.getElementById("post-editor-underline");
const buttonUrl = document.getElementById("post-editor-url");
const buttonCode = document.getElementById("post-editor-code");
const buttonImg = document.getElementById("post-editor-img");
const buttonOl = document.getElementById("post-editor-ol");
const buttonUl = document.getElementById("post-editor-ul");
const buttonSpoiler = document.getElementById("post-editor-spoiler");
function insertTag(tagStart, newline = false, prefill = "") {
const hasAttr = tagStart[tagStart.length - 1] === "=";
let tagEnd = tagStart;
let tagInsertStart = `[${tagStart}]${newline ? "\n" : ""}`;
if (hasAttr) {
tagEnd = tagEnd.slice(0, -1);
}
const tagInsertEnd = `${newline ? "\n" : ""}[/${tagEnd}]`;
const hasSelection = ta.selectionStart !== ta.selectionEnd;
const text = ta.value;
if (hasSelection) {
const realStart = Math.min(ta.selectionStart, ta.selectionEnd);
const realEnd = Math.max(ta.selectionStart, ta.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}`;
ta.value = reconst;
if (!hasAttr){
ta.setSelectionRange(realStart + tagInsertStart.length, realStart + tagInsertStart.length + selectionLength);
} else {
ta.setSelectionRange(realStart + tagInsertEnd.length - 1, realStart + tagInsertEnd.length - 1); // cursor on attr
}
ta.focus()
} else {
if (hasAttr) {
tagInsertStart += prefill;
}
const cursor = ta.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;
}
const reconst = `${strStart}${tagInsertStart}${tagInsertEnd}${strEnd}`;
ta.value = reconst;
ta.setSelectionRange(newCursor, newCursor);
ta.focus()
}
}
buttonBold.addEventListener("click", (e) => {
e.preventDefault();
insertTag("b")
})
buttonItalics.addEventListener("click", (e) => {
e.preventDefault();
insertTag("i")
})
buttonStrike.addEventListener("click", (e) => {
e.preventDefault();
insertTag("s")
})
buttonUnderline.addEventListener("click", (e) => {
e.preventDefault();
insertTag("u")
})
buttonUrl.addEventListener("click", (e) => {
e.preventDefault();
insertTag("url=", false, "link label");
})
buttonCode.addEventListener("click", (e) => {
e.preventDefault();
insertTag("code", true)
})
buttonImg.addEventListener("click", (e) => {
e.preventDefault();
insertTag("img=", false, "alt text");
})
buttonOl.addEventListener("click", (e) => {
e.preventDefault();
insertTag("ol", true);
})
buttonUl.addEventListener("click", (e) => {
e.preventDefault();
insertTag("ul", true);
})
buttonSpoiler.addEventListener("click", (e) => {
e.preventDefault();
insertTag("spoiler=", true, "hidden content");
})
const previewEndpoint = "/api/babycode-preview";
let previousMarkup = "";
const previewTab = document.getElementById("tab-preview");
previewTab.addEventListener("tab-activated", async () => {
const previewContainer = document.getElementById("babycode-preview-container");
const previewErrorsContainer = document.getElementById("babycode-preview-errors-container");
// previewErrorsContainer.textContent = "";
const markup = ta.value.trim();
if (markup === "" || markup === previousMarkup) {
return;
}
const bannedTags = JSON.parse(document.getElementById('babycode-banned-tags').value);
previousMarkup = markup;
const req = await fetch(previewEndpoint, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
markup: markup,
banned_tags: bannedTags,
})
})
if (!req.ok) {
switch (req.status) {
case 429:
previewErrorsContainer.textContent = "(Old preview, try again in a few seconds.)"
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.)"
console.error(req.error);
break;
}
return;
}
const json_resp = await req.json();
previewContainer.innerHTML = json_resp.html;
previewErrorsContainer.textContent = "";
const accordionRefreshEvt = new CustomEvent("refresh_accordions");
document.body.dispatchEvent(accordionRefreshEvt);
});
}

View File

@@ -1,4 +1,7 @@
const bookmarkMenuHrefTemplate = '/hyperapi/bookmarks-dropdown'
const bookmarkMenuHrefTemplate = '/hyperapi/bookmarks-dropdown';
const previewEndpoint = '/api/babycode-preview';
const delay = ms => {return new Promise(resolve => setTimeout(resolve, ms))}
export default class {
async showBookmarkMenu(ev, el) {
@@ -85,4 +88,160 @@ export default class {
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.getString('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 = '';
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 === '' || 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.getString('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();
}
}

View File

@@ -1,13 +1,5 @@
{
const ta = document.getElementById("babycode-content");
for (let button of document.querySelectorAll(".reply-button")) {
button.addEventListener("click", (e) => {
ta.value += button.value;
ta.scrollIntoView()
ta.focus();
})
}
function supportsPopover() {
return Object.hasOwn(HTMLElement.prototype, "popover");

View File

@@ -1,26 +1,3 @@
function activateSelfDeactivateSibs(button) {
if (button.classList.contains("active")) return;
Array.from(button.parentNode.children).forEach(s => {
if (s === button){
button.classList.add('active');
} else {
s.classList.remove('active');
}
const targetId = s.dataset.targetId;
const target = document.getElementById(targetId);
if (!target) return;
if (s.classList.contains('active')) {
target.classList.add('active');
target.dispatchEvent(new CustomEvent("tab-activated", {bubbles: false}))
} else {
target.classList.remove('active');
}
});
}
function openLightbox(post, idx) {
lightboxCurrentPost = post;
lightboxCurrentIdx = idx;
@@ -102,43 +79,6 @@ let lightboxCurrentPost = null;
let lightboxCurrentIdx = -1;
document.addEventListener("DOMContentLoaded", () => {
// tabs
document.querySelectorAll(".tab-button").forEach(button => {
button.addEventListener("click", () => {
activateSelfDeactivateSibs(button);
});
});
// accordions
const handledAccordions = new Set();
function attachAccordionHandlers(accordion){
if(handledAccordions.has(accordion)) {
return;
}
handledAccordions.add(accordion)
const header = accordion.querySelector(".accordion-header");
const toggleButton = header.querySelector(".accordion-toggle");
const content = accordion.querySelector(".accordion-content");
const toggle = (e) => {
e.stopPropagation();
accordion.classList.toggle("hidden");
content.classList.toggle("hidden");
toggleButton.textContent = content.classList.contains("hidden") ? "+" : "-"
}
toggleButton.addEventListener("click", toggle);
}
function refreshAccordions(){
const accordions = document.querySelectorAll(".accordion");
accordions.forEach(attachAccordionHandlers);
}
refreshAccordions()
document.body.addEventListener('refresh_accordions', refreshAccordions)
//lightboxes
lightboxObj = constructLightbox();
document.body.appendChild(lightboxObj.dialog);
@@ -192,13 +132,4 @@ document.addEventListener("DOMContentLoaded", () => {
image.addEventListener("load", () => setImageMaxSize(image));
}
})
// copy code blocks
for (let button of document.querySelectorAll(".copy-code")) {
button.addEventListener("click", async () => {
await navigator.clipboard.writeText(button.value)
button.textContent = "Copied!"
setTimeout(() => {button.textContent = "Copy"}, 1000.0)
})
};
});