diff --git a/src/webpage/emoji.ts b/src/webpage/emoji.ts
index c01192b..5c6421f 100644
--- a/src/webpage/emoji.ts
+++ b/src/webpage/emoji.ts
@@ -134,8 +134,16 @@ class Emoji {
});
const menu = document.createElement("div");
menu.classList.add("flexttb", "emojiPicker");
- menu.style.top = y + "px";
- menu.style.left = x + "px";
+ if (y > 0) {
+ menu.style.top = y + "px";
+ } else {
+ menu.style.bottom = y * -1 + "px";
+ }
+ if (x > 0) {
+ menu.style.left = x + "px";
+ } else {
+ menu.style.right = x * -1 + "px";
+ }
const topBar = document.createElement("div");
topBar.classList.add("flexltr", "emojiHeading");
diff --git a/src/webpage/index.html b/src/webpage/index.html
index 51cc205..55ef721 100644
--- a/src/webpage/index.html
+++ b/src/webpage/index.html
@@ -85,10 +85,10 @@
+
-
diff --git a/src/webpage/index.ts b/src/webpage/index.ts
index e6458e7..2878ad5 100644
--- a/src/webpage/index.ts
+++ b/src/webpage/index.ts
@@ -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();
})();
diff --git a/src/webpage/localuser.ts b/src/webpage/localuser.ts
index 747b91a..0119d6c 100644
--- a/src/webpage/localuser.ts
+++ b/src/webpage/localuser.ts
@@ -20,7 +20,7 @@ import {
} from "./jsontypes.js";
import {Member} from "./member.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 {Role} from "./role.js";
import {VoiceFactory} from "./voice.js";
@@ -33,7 +33,9 @@ import {Rights} from "./rights.js";
import {Contextmenu} from "./contextmenu.js";
const wsCodesRetry = new Set([4000, 4001, 4002, 4003, 4005, 4007, 4008, 4009]);
-
+interface CustomHTMLDivElement extends HTMLDivElement {
+ markdown: MarkDown;
+}
class Localuser {
badges = new Map<
string,
@@ -2229,10 +2231,6 @@ class Localuser {
}
readonly autofillregex = Object.freeze(/[@#:]([a-z0-9 ]*)$/i);
mdBox() {
- interface CustomHTMLDivElement extends HTMLDivElement {
- markdown: MarkDown;
- }
-
const typebox = document.getElementById("typebox") as CustomHTMLDivElement;
const typeMd = typebox.markdown;
typeMd.owner = this;
@@ -2240,18 +2238,57 @@ class Localuser {
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;
- raw = raw.split(original)[1];
- if (raw === undefined) return;
- raw = original.replace(this.autofillregex, "") + replacewith + raw;
+ let empty = raw.length === 0;
+ raw = original !== "" ? raw.split(original)[1] : raw;
+ if (raw === undefined && !empty) return;
+ if (empty) {
+ raw = "";
+ }
+ raw = (start ? original.replace(start, "") : original) + replacewith + raw;
console.log(raw);
console.log(replacewith);
console.log(original);
typebox.txt = raw.split("");
- const match = original.match(this.autofillregex);
+ console.log(typebox.rawString);
+ const match = start ? original.match(start) : true;
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(
@@ -2339,8 +2376,7 @@ class Localuser {
break;
case "Enter":
case "Tab":
- //@ts-ignore
- cur.onclick();
+ cur.click();
break;
}
return true;
@@ -2437,6 +2473,7 @@ class Localuser {
await Member.new(thing, this.lookingguild as Guild);
}
}
+ if (!typebox.rawString.startsWith(original)) return;
this.MDFineMentionGen(name, original, box, typebox);
}
});
@@ -2475,6 +2512,8 @@ class Localuser {
this.MDSearchOptions([], "", box, md);
}
break;
+ default:
+ return;
}
return;
}
diff --git a/src/webpage/markdown.ts b/src/webpage/markdown.ts
index 76ce56e..5ba18dc 100644
--- a/src/webpage/markdown.ts
+++ b/src/webpage/markdown.ts
@@ -46,6 +46,9 @@ class MarkDown {
get textContent() {
return this.makeHTML().textContent;
}
+ static getText() {
+ return text;
+ }
makeHTML({keep = this.keep, stdsize = this.stdsize} = {}) {
return this.markdown(this.txt, {keep, stdsize});
}
@@ -67,6 +70,9 @@ class MarkDown {
current = document.createElement("span");
}
}
+ function getCurLast(): Element | undefined {
+ return span.children[span.children.length - 1];
+ }
for (let i = 0; i < txt.length; i++) {
if (txt[i] === "\n" || i === 0) {
const first = i === 0;
@@ -143,6 +149,10 @@ class MarkDown {
}
if (txt[i] === "\n") {
if (!stdsize) {
+ const last = getCurLast();
+ if (last instanceof HTMLElement && last.contentEditable === "false") {
+ span.append(document.createElement("span"));
+ }
appendcurrent();
span.append(document.createElement("br"));
}
@@ -460,6 +470,13 @@ class MarkDown {
if (txt[j] === ">") {
appendcurrent();
+ const last = getCurLast();
+ if (
+ last instanceof HTMLBRElement ||
+ (last instanceof HTMLElement && last.contentEditable === "false")
+ ) {
+ span.append(document.createElement("span"));
+ }
const mention = document.createElement("span");
mention.classList.add("mentionMD");
mention.contentEditable = "false";
@@ -680,6 +697,15 @@ class MarkDown {
current.textContent += txt[i];
}
appendcurrent();
+ const last = getCurLast();
+ if (
+ last &&
+ last instanceof HTMLElement &&
+ last.contentEditable &&
+ !(last instanceof HTMLBRElement)
+ ) {
+ span.append(current);
+ }
return span;
}
static unspoil(e: any): void {
@@ -696,7 +722,8 @@ class MarkDown {
};
let prevcontent = "";
box.onkeyup = (_) => {
- const content = MarkDown.gatherBoxText(box);
+ let content = MarkDown.gatherBoxText(box);
+ if (content === "\n") content = "";
if (content !== prevcontent) {
prevcontent = content;
this.txt = content.split("");
@@ -725,14 +752,19 @@ class MarkDown {
) {
this.customBox = [stringToHTML, HTMLToString];
}
- boxupdate(offset = 0) {
+ boxupdate(offset = 0, allowLazy = true, computedLength: void | number = undefined) {
const box = this.box.deref();
if (!box) return;
let restore: undefined | (() => void);
if (this.customBox) {
- restore = saveCaretPosition(box, offset, this.customBox[1]);
+ restore = saveCaretPosition(box, offset, this.customBox[1], computedLength);
} else {
- restore = saveCaretPosition(box, offset);
+ restore = saveCaretPosition(
+ box,
+ offset,
+ MarkDown.gatherBoxText.bind(MarkDown),
+ computedLength,
+ );
}
if (this.customBox) {
@@ -748,10 +780,13 @@ class MarkDown {
html.childNodes[0].childNodes[0];
//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
- 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");
} 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.append(html);
} else {
@@ -762,13 +797,6 @@ class MarkDown {
}
if (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);
}
@@ -864,6 +892,7 @@ function saveCaretPosition(
context: HTMLElement,
offset = 0,
txtLengthFunc = MarkDown.gatherBoxText.bind(MarkDown),
+ computedLength: void | number = undefined,
) {
const selection = window.getSelection() as Selection;
if (!selection) return;
@@ -873,26 +902,39 @@ function saveCaretPosition(
let base = selection.anchorNode as Node;
range.setStart(base, 0);
let baseString: string;
- if (!(base instanceof Text)) {
- let i = 0;
- const index = selection.focusOffset;
- //@ts-ignore
- for (const thing of base.childNodes) {
- if (i === index) {
- base = thing;
- break;
- }
- i++;
+ let i = 0;
+ const index = selection.focusOffset;
+
+ for (const thing of Array.from(base.childNodes)) {
+ if (i === index) {
+ base = thing;
+ break;
}
+ 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) {
baseString = txtLengthFunc(base);
} else {
- baseString = base.textContent as string;
+ baseString = base.textContent || "";
}
} else {
baseString = selection.toString();
}
-
range.setStart(context, 0);
let build = "";
@@ -937,8 +979,14 @@ function saveCaretPosition(
build += baseString;
}
text = build;
- let len = build.length + offset;
+ len += build.length;
+ if (computedLength !== undefined) {
+ len = computedLength;
+ }
len = Math.min(len, txtLengthFunc(context).length);
+ console.log(len, base);
+ len += offset;
+ console.log(len);
return function restore() {
if (!selection) return;
const pos = getTextNodeAtPosition(context, len, txtLengthFunc);
@@ -960,6 +1008,12 @@ function getTextNodeAtPosition(
node: Node;
position: number;
} {
+ if (index === 0) {
+ return {
+ node: root,
+ position: 0,
+ };
+ }
if (root instanceof Text) {
return {
node: root,
@@ -976,6 +1030,7 @@ function getTextNodeAtPosition(
position: -1,
};
}
+
let lastElm: Node = root;
for (const node of root.childNodes as unknown as Node[]) {
lastElm = node;
@@ -983,10 +1038,31 @@ function getTextNodeAtPosition(
if (node instanceof HTMLElement) {
len = txtLengthFunc(node).length;
} else {
- len = (node.textContent as string).length;
+ len = (node.textContent || "").length;
}
if (len <= index && (len < index || len !== 0)) {
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 {
const returny = getTextNodeAtPosition(node, index);
if (returny.position === -1) {
diff --git a/src/webpage/style.css b/src/webpage/style.css
index bdbd582..15d3cd3 100644
--- a/src/webpage/style.css
+++ b/src/webpage/style.css
@@ -432,6 +432,11 @@ textarea {
mask-repeat: no-repeat;
aspect-ratio: 1/1;
}
+#emojiTB{
+ width:.2in;
+ height:.2in;
+ cursor: pointer;
+}
.selectarrow {
position: absolute;
top: 10px;
@@ -1143,6 +1148,7 @@ span.instanceStatus {
flex-grow: 1;
width: 100%;
margin-left: 0.06in;
+ white-space: pre;
}
.outerTypeBox {
max-height: 50svh;
@@ -1499,7 +1505,7 @@ span .quote:last-of-type .quoteline {
cursor: pointer;
}
.mentionMD::after {
- content: "";
+ /* content: ""; */
}
.mentionMD:hover {
background: var(--mention);