emoji button and a lot of bug fixes

This commit is contained in:
MathMan05 2025-04-05 20:55:18 -05:00
parent a33755f6ae
commit fbd7c38f44
6 changed files with 182 additions and 45 deletions

View file

@ -134,8 +134,16 @@ class Emoji {
}); });
const menu = document.createElement("div"); const menu = document.createElement("div");
menu.classList.add("flexttb", "emojiPicker"); menu.classList.add("flexttb", "emojiPicker");
if (y > 0) {
menu.style.top = y + "px"; menu.style.top = y + "px";
} else {
menu.style.bottom = y * -1 + "px";
}
if (x > 0) {
menu.style.left = x + "px"; menu.style.left = x + "px";
} else {
menu.style.right = x * -1 + "px";
}
const topBar = document.createElement("div"); const topBar = document.createElement("div");
topBar.classList.add("flexltr", "emojiHeading"); topBar.classList.add("flexltr", "emojiHeading");

View file

@ -85,10 +85,10 @@
<div id="channelw" class="flexltr"> <div id="channelw" class="flexltr">
<div id="loadingdiv"></div> <div id="loadingdiv"></div>
</div> </div>
<div id="ghostMessages"></div>
<div style="position: relative"> <div style="position: relative">
<div id="searchOptions" class="flexttb searchOptions"></div> <div id="searchOptions" class="flexttb searchOptions"></div>
</div> </div>
<div id="ghostMessages"></div>
<div id="pasteimage" class="flexltr"></div> <div id="pasteimage" class="flexltr"></div>
<div id="replybox" class="hideReplyBox"></div> <div id="replybox" class="hideReplyBox"></div>
<div id="typediv"> <div id="typediv">
@ -96,6 +96,7 @@
<div class="outerTypeBox"> <div class="outerTypeBox">
<span class="svg-upload svgicon" id="upload"></span> <span class="svg-upload svgicon" id="upload"></span>
<div id="typebox" contenteditable="true"></div> <div id="typebox" contenteditable="true"></div>
<span class="svgicon svg-emoji" id="emojiTB"></span>
</div> </div>
</div> </div>
<div id="typing" class="hidden flexltr"> <div id="typing" class="hidden flexltr">

View file

@ -267,4 +267,11 @@ import {I18n} from "./i18n.js";
} }
}; };
}; };
const emojiTB = document.getElementById("emojiTB") as HTMLElement;
emojiTB.onmousedown = (e) => {
e.preventDefault();
e.stopImmediatePropagation();
thisUser.TBEmojiMenu(emojiTB.getBoundingClientRect());
};
emojiTB.onclick = (e) => e.stopImmediatePropagation();
})(); })();

View file

@ -20,7 +20,7 @@ import {
} from "./jsontypes.js"; } from "./jsontypes.js";
import {Member} from "./member.js"; import {Member} from "./member.js";
import {Dialog, Form, FormError, Options, Settings} from "./settings.js"; import {Dialog, Form, FormError, Options, Settings} from "./settings.js";
import {getTextNodeAtPosition, MarkDown} from "./markdown.js"; import {getTextNodeAtPosition, MarkDown, saveCaretPosition} from "./markdown.js";
import {Bot} from "./bot.js"; import {Bot} from "./bot.js";
import {Role} from "./role.js"; import {Role} from "./role.js";
import {VoiceFactory} from "./voice.js"; import {VoiceFactory} from "./voice.js";
@ -33,7 +33,9 @@ import {Rights} from "./rights.js";
import {Contextmenu} from "./contextmenu.js"; import {Contextmenu} from "./contextmenu.js";
const wsCodesRetry = new Set([4000, 4001, 4002, 4003, 4005, 4007, 4008, 4009]); const wsCodesRetry = new Set([4000, 4001, 4002, 4003, 4005, 4007, 4008, 4009]);
interface CustomHTMLDivElement extends HTMLDivElement {
markdown: MarkDown;
}
class Localuser { class Localuser {
badges = new Map< badges = new Map<
string, string,
@ -2229,10 +2231,6 @@ class Localuser {
} }
readonly autofillregex = Object.freeze(/[@#:]([a-z0-9 ]*)$/i); readonly autofillregex = Object.freeze(/[@#:]([a-z0-9 ]*)$/i);
mdBox() { mdBox() {
interface CustomHTMLDivElement extends HTMLDivElement {
markdown: MarkDown;
}
const typebox = document.getElementById("typebox") as CustomHTMLDivElement; const typebox = document.getElementById("typebox") as CustomHTMLDivElement;
const typeMd = typebox.markdown; const typeMd = typebox.markdown;
typeMd.owner = this; typeMd.owner = this;
@ -2240,18 +2238,57 @@ class Localuser {
this.search(document.getElementById("searchOptions") as HTMLDivElement, typeMd, str, pre); this.search(document.getElementById("searchOptions") as HTMLDivElement, typeMd, str, pre);
}; };
} }
MDReplace(replacewith: string, original: string, typebox: MarkDown) { async TBEmojiMenu(rect: DOMRect) {
const typebox = document.getElementById("typebox") as CustomHTMLDivElement;
const p = saveCaretPosition(typebox);
if (!p) return;
const original = MarkDown.getText();
console.log(original);
const emoji = await Emoji.emojiPicker(
-10 + rect.left - window.screen.width,
-5 + rect.top - window.screen.height,
this,
);
p();
const md = typebox.markdown;
this.MDReplace(
emoji.id
? `<${emoji.animated ? "a" : ""}:${emoji.name}:${emoji.id}>`
: (emoji.emoji as string),
original,
md,
null,
);
//*/
}
MDReplace(
replacewith: string,
original: string,
typebox: MarkDown,
start: RegExp | null = this.autofillregex,
) {
let raw = typebox.rawString; let raw = typebox.rawString;
raw = raw.split(original)[1]; let empty = raw.length === 0;
if (raw === undefined) return; raw = original !== "" ? raw.split(original)[1] : raw;
raw = original.replace(this.autofillregex, "") + replacewith + raw; if (raw === undefined && !empty) return;
if (empty) {
raw = "";
}
raw = (start ? original.replace(start, "") : original) + replacewith + raw;
console.log(raw); console.log(raw);
console.log(replacewith); console.log(replacewith);
console.log(original); console.log(original);
typebox.txt = raw.split(""); typebox.txt = raw.split("");
const match = original.match(this.autofillregex); console.log(typebox.rawString);
const match = start ? original.match(start) : true;
if (match) { if (match) {
typebox.boxupdate(replacewith.length - match[0].length); console.log(match);
typebox.boxupdate(
replacewith.length - (match === true ? 0 : match[0].length),
false,
original.length,
);
} }
} }
MDSearchOptions( MDSearchOptions(
@ -2339,8 +2376,7 @@ class Localuser {
break; break;
case "Enter": case "Enter":
case "Tab": case "Tab":
//@ts-ignore cur.click();
cur.onclick();
break; break;
} }
return true; return true;
@ -2437,6 +2473,7 @@ class Localuser {
await Member.new(thing, this.lookingguild as Guild); await Member.new(thing, this.lookingguild as Guild);
} }
} }
if (!typebox.rawString.startsWith(original)) return;
this.MDFineMentionGen(name, original, box, typebox); this.MDFineMentionGen(name, original, box, typebox);
} }
}); });
@ -2475,6 +2512,8 @@ class Localuser {
this.MDSearchOptions([], "", box, md); this.MDSearchOptions([], "", box, md);
} }
break; break;
default:
return;
} }
return; return;
} }

View file

@ -46,6 +46,9 @@ class MarkDown {
get textContent() { get textContent() {
return this.makeHTML().textContent; return this.makeHTML().textContent;
} }
static getText() {
return text;
}
makeHTML({keep = this.keep, stdsize = this.stdsize} = {}) { makeHTML({keep = this.keep, stdsize = this.stdsize} = {}) {
return this.markdown(this.txt, {keep, stdsize}); return this.markdown(this.txt, {keep, stdsize});
} }
@ -67,6 +70,9 @@ class MarkDown {
current = document.createElement("span"); current = document.createElement("span");
} }
} }
function getCurLast(): Element | undefined {
return span.children[span.children.length - 1];
}
for (let i = 0; i < txt.length; i++) { for (let i = 0; i < txt.length; i++) {
if (txt[i] === "\n" || i === 0) { if (txt[i] === "\n" || i === 0) {
const first = i === 0; const first = i === 0;
@ -143,6 +149,10 @@ class MarkDown {
} }
if (txt[i] === "\n") { if (txt[i] === "\n") {
if (!stdsize) { if (!stdsize) {
const last = getCurLast();
if (last instanceof HTMLElement && last.contentEditable === "false") {
span.append(document.createElement("span"));
}
appendcurrent(); appendcurrent();
span.append(document.createElement("br")); span.append(document.createElement("br"));
} }
@ -460,6 +470,13 @@ class MarkDown {
if (txt[j] === ">") { if (txt[j] === ">") {
appendcurrent(); appendcurrent();
const last = getCurLast();
if (
last instanceof HTMLBRElement ||
(last instanceof HTMLElement && last.contentEditable === "false")
) {
span.append(document.createElement("span"));
}
const mention = document.createElement("span"); const mention = document.createElement("span");
mention.classList.add("mentionMD"); mention.classList.add("mentionMD");
mention.contentEditable = "false"; mention.contentEditable = "false";
@ -680,6 +697,15 @@ class MarkDown {
current.textContent += txt[i]; current.textContent += txt[i];
} }
appendcurrent(); appendcurrent();
const last = getCurLast();
if (
last &&
last instanceof HTMLElement &&
last.contentEditable &&
!(last instanceof HTMLBRElement)
) {
span.append(current);
}
return span; return span;
} }
static unspoil(e: any): void { static unspoil(e: any): void {
@ -696,7 +722,8 @@ class MarkDown {
}; };
let prevcontent = ""; let prevcontent = "";
box.onkeyup = (_) => { box.onkeyup = (_) => {
const content = MarkDown.gatherBoxText(box); let content = MarkDown.gatherBoxText(box);
if (content === "\n") content = "";
if (content !== prevcontent) { if (content !== prevcontent) {
prevcontent = content; prevcontent = content;
this.txt = content.split(""); this.txt = content.split("");
@ -725,14 +752,19 @@ class MarkDown {
) { ) {
this.customBox = [stringToHTML, HTMLToString]; this.customBox = [stringToHTML, HTMLToString];
} }
boxupdate(offset = 0) { boxupdate(offset = 0, allowLazy = true, computedLength: void | number = undefined) {
const box = this.box.deref(); const box = this.box.deref();
if (!box) return; if (!box) return;
let restore: undefined | (() => void); let restore: undefined | (() => void);
if (this.customBox) { if (this.customBox) {
restore = saveCaretPosition(box, offset, this.customBox[1]); restore = saveCaretPosition(box, offset, this.customBox[1], computedLength);
} else { } else {
restore = saveCaretPosition(box, offset); restore = saveCaretPosition(
box,
offset,
MarkDown.gatherBoxText.bind(MarkDown),
computedLength,
);
} }
if (this.customBox) { if (this.customBox) {
@ -748,10 +780,13 @@ class MarkDown {
html.childNodes[0].childNodes[0]; html.childNodes[0].childNodes[0];
//console.log(box.cloneNode(true), html.cloneNode(true)); //console.log(box.cloneNode(true), html.cloneNode(true));
//TODO this may be slow, may want to check in on this in the future if it is //TODO this may be slow, may want to check in on this in the future if it is
if (!box.hasChildNodes() || html.isEqualNode(Array.from(box.childNodes)[0])) { if ((!box.hasChildNodes() || html.isEqualNode(Array.from(box.childNodes)[0])) && allowLazy) {
//console.log("no replace needed"); //console.log("no replace needed");
} else { } else {
if (!(box.childNodes.length === 1 && box.childNodes[0] instanceof Text && condition)) { if (
!(box.childNodes.length === 1 && box.childNodes[0] instanceof Text && condition) ||
!allowLazy
) {
box.innerHTML = ""; box.innerHTML = "";
box.append(html); box.append(html);
} else { } else {
@ -762,13 +797,6 @@ class MarkDown {
} }
if (restore) { if (restore) {
restore(); restore();
if (this.customBox) {
const test = saveCaretPosition(box, 0, this.customBox[1]);
if (test) test();
} else {
const test = saveCaretPosition(box);
if (test) test();
}
} }
this.onUpdate(text, formatted); this.onUpdate(text, formatted);
} }
@ -864,6 +892,7 @@ function saveCaretPosition(
context: HTMLElement, context: HTMLElement,
offset = 0, offset = 0,
txtLengthFunc = MarkDown.gatherBoxText.bind(MarkDown), txtLengthFunc = MarkDown.gatherBoxText.bind(MarkDown),
computedLength: void | number = undefined,
) { ) {
const selection = window.getSelection() as Selection; const selection = window.getSelection() as Selection;
if (!selection) return; if (!selection) return;
@ -873,26 +902,39 @@ function saveCaretPosition(
let base = selection.anchorNode as Node; let base = selection.anchorNode as Node;
range.setStart(base, 0); range.setStart(base, 0);
let baseString: string; let baseString: string;
if (!(base instanceof Text)) {
let i = 0; let i = 0;
const index = selection.focusOffset; const index = selection.focusOffset;
//@ts-ignore
for (const thing of base.childNodes) { for (const thing of Array.from(base.childNodes)) {
if (i === index) { if (i === index) {
base = thing; base = thing;
break; break;
} }
i++; i++;
} }
console.log(base);
const prev = base.previousSibling;
let len = 0;
if ((!prev || prev instanceof HTMLBRElement) && base instanceof HTMLBRElement) {
len--;
}
if (
!(base instanceof Text) &&
!(
base instanceof HTMLSpanElement &&
base.className === "" &&
base.children.length == 0 &&
!(base instanceof HTMLBRElement)
)
) {
if (base instanceof HTMLElement) { if (base instanceof HTMLElement) {
baseString = txtLengthFunc(base); baseString = txtLengthFunc(base);
} else { } else {
baseString = base.textContent as string; baseString = base.textContent || "";
} }
} else { } else {
baseString = selection.toString(); baseString = selection.toString();
} }
range.setStart(context, 0); range.setStart(context, 0);
let build = ""; let build = "";
@ -937,8 +979,14 @@ function saveCaretPosition(
build += baseString; build += baseString;
} }
text = build; text = build;
let len = build.length + offset; len += build.length;
if (computedLength !== undefined) {
len = computedLength;
}
len = Math.min(len, txtLengthFunc(context).length); len = Math.min(len, txtLengthFunc(context).length);
console.log(len, base);
len += offset;
console.log(len);
return function restore() { return function restore() {
if (!selection) return; if (!selection) return;
const pos = getTextNodeAtPosition(context, len, txtLengthFunc); const pos = getTextNodeAtPosition(context, len, txtLengthFunc);
@ -960,6 +1008,12 @@ function getTextNodeAtPosition(
node: Node; node: Node;
position: number; position: number;
} { } {
if (index === 0) {
return {
node: root,
position: 0,
};
}
if (root instanceof Text) { if (root instanceof Text) {
return { return {
node: root, node: root,
@ -976,6 +1030,7 @@ function getTextNodeAtPosition(
position: -1, position: -1,
}; };
} }
let lastElm: Node = root; let lastElm: Node = root;
for (const node of root.childNodes as unknown as Node[]) { for (const node of root.childNodes as unknown as Node[]) {
lastElm = node; lastElm = node;
@ -983,10 +1038,31 @@ function getTextNodeAtPosition(
if (node instanceof HTMLElement) { if (node instanceof HTMLElement) {
len = txtLengthFunc(node).length; len = txtLengthFunc(node).length;
} else { } else {
len = (node.textContent as string).length; len = (node.textContent || "").length;
} }
if (len <= index && (len < index || len !== 0)) { if (len <= index && (len < index || len !== 0)) {
index -= len; index -= len;
if (index === 0) {
let nodey = node;
let bad = false;
while (nodey.childNodes.length) {
nodey = Array.from(nodey.childNodes).at(-1) as ChildNode;
if (nodey instanceof HTMLElement && nodey.contentEditable === "false") {
bad = true;
break;
}
}
if (
!(nodey instanceof HTMLBRElement) &&
(!(nodey instanceof HTMLElement) || nodey.contentEditable === "false") &&
!bad
) {
return {
node: nodey,
position: (nodey.textContent || "").length,
};
}
}
} else { } else {
const returny = getTextNodeAtPosition(node, index); const returny = getTextNodeAtPosition(node, index);
if (returny.position === -1) { if (returny.position === -1) {

View file

@ -432,6 +432,11 @@ textarea {
mask-repeat: no-repeat; mask-repeat: no-repeat;
aspect-ratio: 1/1; aspect-ratio: 1/1;
} }
#emojiTB{
width:.2in;
height:.2in;
cursor: pointer;
}
.selectarrow { .selectarrow {
position: absolute; position: absolute;
top: 10px; top: 10px;
@ -1143,6 +1148,7 @@ span.instanceStatus {
flex-grow: 1; flex-grow: 1;
width: 100%; width: 100%;
margin-left: 0.06in; margin-left: 0.06in;
white-space: pre;
} }
.outerTypeBox { .outerTypeBox {
max-height: 50svh; max-height: 50svh;
@ -1499,7 +1505,7 @@ span .quote:last-of-type .quoteline {
cursor: pointer; cursor: pointer;
} }
.mentionMD::after { .mentionMD::after {
content: ""; /* content: ""; */
} }
.mentionMD:hover { .mentionMD:hover {
background: var(--mention); background: var(--mention);