way improved editing of messages

This commit is contained in:
MathMan05 2024-11-25 14:39:38 -06:00
parent c1645099b8
commit ce538b3909
8 changed files with 204 additions and 145 deletions

View file

@ -791,6 +791,15 @@ class Channel extends SnowFlake{
return new Message(json[0], this);
}
}
editLast(){
let message:Message|undefined=this.lastmessage;
while(message&&message.author!==this.localuser.user){
message=this.messages.get(this.idToPrev.get(message.id) as string);
}
if(message){
message.setEdit();
}
}
static genid: number = 0;
async getHTML(){
const id = ++Channel.genid;
@ -842,6 +851,7 @@ class Channel extends SnowFlake{
await this.buildmessages();
//loading.classList.remove("loading");
(document.getElementById("typebox") as HTMLDivElement).contentEditable =""+this.canMessage;
(document.getElementById("typebox") as HTMLDivElement).focus();
}
typingmap: Map<Member, number> = new Map();
async typingStart(typing: startTypingjson): Promise<void>{

View file

@ -197,6 +197,7 @@ class Group extends Channel{
}
this.buildmessages();
(document.getElementById("typebox") as HTMLDivElement).contentEditable ="" + true;
(document.getElementById("typebox") as HTMLDivElement).focus();
}
messageCreate(messagep: { d: messagejson }){
const messagez = new Message(messagep.d, this);

View file

@ -75,7 +75,7 @@
</div>
</div>
<div style="position: relative;">
<div id="searchOptions" class="flexttb"></div>
<div id="searchOptions" class="flexttb searchOptions"></div>
</div>
<div id="pasteimage" class="flexltr"></div>
<div id="replybox" class="hideReplyBox"></div>

View file

@ -153,19 +153,15 @@ import { I18n } from "./i18n.js";
if(thisUser.keyup(event)){return}
const channel = thisUser.channelfocus;
if(!channel)return;
if(markdown.rawString===""&&event.key==="ArrowUp"){
channel.editLast();
return;
}
channel.typingstart();
if(event.key === "Enter" && !event.shiftKey){
event.preventDefault();
if(channel.editing){
channel.editing.edit(markdown.rawString);
channel.editing = null;
}else{
replyingTo = thisUser.channelfocus
? thisUser.channelfocus.replyingto
: null;
replyingTo = thisUser.channelfocus? thisUser.channelfocus.replyingto: null;
if(replyingTo?.div){
replyingTo.div.classList.remove("replying");
}
@ -181,8 +177,6 @@ import { I18n } from "./i18n.js";
if(thisUser.channelfocus){
thisUser.channelfocus.makereplybox();
}
}
while(images.length){
images.pop();
pasteImageElement.removeChild(imagesHtml.pop() as HTMLElement);

View file

@ -1717,34 +1717,32 @@ class Localuser{
Bot.InviteMaker(appId,form,this.info);
})
}
typeMd?:MarkDown;
readonly autofillregex=Object.freeze(/[@#:]([a-z0-9 ]*)$/i);
mdBox(){
interface CustomHTMLDivElement extends HTMLDivElement {markdown: MarkDown;}
const typebox = document.getElementById("typebox") as CustomHTMLDivElement;
this.typeMd=typebox.markdown;
this.typeMd.owner=this;
this.typeMd.onUpdate=this.search.bind(this);
const typeMd=typebox.markdown;
typeMd.owner=this;
typeMd.onUpdate=(str,pre)=>{
this.search(document.getElementById("searchOptions") as HTMLDivElement,typeMd,str,pre);
}
MDReplace(replacewith:string,original:string){
const typebox = document.getElementById("typebox") as HTMLDivElement;
if(!this.typeMd)return;
let raw=this.typeMd.rawString;
}
MDReplace(replacewith:string,original:string,typebox:MarkDown){
let raw=typebox.rawString;
raw=raw.split(original)[1];
if(raw===undefined) return;
raw=original.replace(this.autofillregex,"")+replacewith+raw;
console.log(raw);
console.log(replacewith);
console.log(original);
this.typeMd.txt = raw.split("");
typebox.txt = raw.split("");
const match=original.match(this.autofillregex);
if(match){
this.typeMd.boxupdate(typebox,replacewith.length-match[0].length);
typebox.boxupdate(replacewith.length-match[0].length);
}
}
MDSearchOptions(options:[string,string,void|HTMLElement][],original:string){
const div=document.getElementById("searchOptions");
MDSearchOptions(options:[string,string,void|HTMLElement][],original:string,div:HTMLDivElement,typebox:MarkDown){
if(!div)return;
div.innerHTML="";
let i=0;
@ -1757,7 +1755,6 @@ class Localuser{
const span=document.createElement("span");
htmloptions.push(span);
if(thing[2]){
console.log(thing);
span.append(thing[2]);
}
@ -1766,19 +1763,21 @@ class Localuser{
if(e){
const selection = window.getSelection() as Selection;
const typebox = document.getElementById("typebox") as HTMLDivElement;
const box=typebox.box.deref();
if(!box) return;
if(selection){
console.warn(original);
const pos = getTextNodeAtPosition(typebox, original.length-(original.match(this.autofillregex) as RegExpMatchArray)[0].length+thing[1].length);
const pos = getTextNodeAtPosition(box, original.length-(original.match(this.autofillregex) as RegExpMatchArray)[0].length+thing[1].length);
selection.removeAllRanges();
const range = new Range();
range.setStart(pos.node, pos.position);
selection.addRange(range);
}
e.preventDefault();
typebox.focus();
box.focus();
}
this.MDReplace(thing[1],original);
this.MDReplace(thing[1],original,typebox);
div.innerHTML="";
remove();
}
@ -1837,7 +1836,7 @@ class Localuser{
remove();
}
}
MDFindChannel(name:string,orginal:string){
MDFindChannel(name:string,orginal:string,box:HTMLDivElement,typebox:MarkDown){
const maybe:[number,Channel][]=[];
if(this.lookingguild&&this.lookingguild.id!=="@me"){
for(const channel of this.lookingguild.channels){
@ -1848,7 +1847,7 @@ class Localuser{
}
}
maybe.sort((a,b)=>b[0]-a[0]);
this.MDSearchOptions(maybe.map((a)=>["# "+a[1].name,`<#${a[1].id}> `,undefined]),orginal);
this.MDSearchOptions(maybe.map((a)=>["# "+a[1].name,`<#${a[1].id}> `,undefined]),orginal,box,typebox);
}
async getUser(id:string){
if(this.userMap.has(id)){
@ -1856,7 +1855,7 @@ class Localuser{
}
return new User(await (await fetch(this.info.api+"/users/"+id)).json(),this);
}
MDFineMentionGen(name:string,original:string){
MDFineMentionGen(name:string,original:string,box:HTMLDivElement,typebox:MarkDown){
let members:[Member,number][]=[];
if(this.lookingguild){
for(const member of this.lookingguild.members){
@ -1867,11 +1866,11 @@ class Localuser{
}
}
members.sort((a,b)=>b[1]-a[1]);
this.MDSearchOptions(members.map((a)=>["@"+a[0].name,`<@${a[0].id}> `,undefined]),original);
this.MDSearchOptions(members.map((a)=>["@"+a[0].name,`<@${a[0].id}> `,undefined]),original,box,typebox);
}
MDFindMention(name:string,original:string){
MDFindMention(name:string,original:string,box:HTMLDivElement,typebox:MarkDown){
if(this.ws&&this.lookingguild){
this.MDFineMentionGen(name,original);
this.MDFineMentionGen(name,original,box,typebox);
const nonce=Math.floor(Math.random()*10**8)+"";
if(this.lookingguild.member_count<=this.lookingguild.members.size) return;
this.ws.send(JSON.stringify(
@ -1906,19 +1905,19 @@ class Localuser{
await Member.new(thing,this.lookingguild as Guild)
}
}
this.MDFineMentionGen(name,original);
this.MDFineMentionGen(name,original,box,typebox);
}
})
}
}
findEmoji(search:string,orginal:string){
findEmoji(search:string,orginal:string,box:HTMLDivElement,typebox:MarkDown){
const emj=Emoji.searchEmoji(search,this,10);
const map=emj.map(([emoji]):[string,string,HTMLElement]=>{
return [emoji.name,emoji.id?`<${emoji.animated?"a":""}:${emoji.name}:${emoji.id}>`:emoji.emoji as string,emoji.getHTML()]
})
this.MDSearchOptions(map,orginal);
this.MDSearchOptions(map,orginal,box,typebox);
}
search(str:string,pre:boolean){
search(box:HTMLDivElement,md:MarkDown,str:string,pre:boolean){
if(!pre){
const match=str.match(this.autofillregex);
@ -1926,25 +1925,23 @@ class Localuser{
const [type, search]=[match[0][0],match[0].split(/@|#|:/)[1]];
switch(type){
case "#":
this.MDFindChannel(search,str);
this.MDFindChannel(search,str,box,md);
break;
case "@":
this.MDFindMention(search,str);
this.MDFindMention(search,str,box,md);
break;
case ":":
if(search.length>=2){
this.findEmoji(search,str)
this.findEmoji(search,str,box,md)
}else{
this.MDSearchOptions([],"");
this.MDSearchOptions([],"",box,md);
}
break;
}
return
}
}
const div=document.getElementById("searchOptions");
if(!div)return;
div.innerHTML="";
box.innerHTML="";
}
keydown:(event:KeyboardEvent)=>unknown=()=>{};
keyup:(event:KeyboardEvent)=>boolean=()=>false;

View file

@ -686,7 +686,9 @@ txt[j + 1] === undefined)
e.target.classList.add("unspoiled");
}
onUpdate:(upto:string,pre:boolean)=>unknown=()=>{};
box=new WeakRef(document.createElement("div"));
giveBox(box: HTMLDivElement,onUpdate:(upto:string,pre:boolean)=>unknown=()=>{}){
this.box=new WeakRef(box);
this.onUpdate=onUpdate;
box.onkeydown = _=>{
//console.log(_);
@ -697,7 +699,7 @@ txt[j + 1] === undefined)
if(content !== prevcontent){
prevcontent = content;
this.txt = content.split("");
this.boxupdate(box);
this.boxupdate();
MarkDown.gatherBoxText(box);
}
@ -713,7 +715,9 @@ txt[j + 1] === undefined)
box.onkeyup(new KeyboardEvent("_"));
};
}
boxupdate(box: HTMLElement,offset=0){
boxupdate(offset=0){
const box=this.box.deref();
if(!box) return;
const restore = saveCaretPosition(box,offset);
box.innerHTML = "";
box.append(this.makeHTML({ keep: true }));
@ -832,7 +836,9 @@ let formatted=false;
function saveCaretPosition(context: HTMLElement,offset=0){
const selection = window.getSelection() as Selection;
if(!selection)return;
try{
const range = selection.getRangeAt(0);
let base=selection.anchorNode as Node;
range.setStart(base, 0);
let baseString:string;
@ -893,7 +899,7 @@ function saveCaretPosition(context: HTMLElement,offset=0){
console.error(node,"This shouldn't happen")
}
}else{
console.error(node,"This shouldn't happen");
//console.error(node,"This shouldn't happen");
}
}
}
@ -902,7 +908,8 @@ function saveCaretPosition(context: HTMLElement,offset=0){
build+=baseString;
}
text=build;
const len=build.length+offset;
let len=build.length+offset;
len=Math.min(len,MarkDown.gatherBoxText(context).length)
return function restore(){
if(!selection)return;
const pos = getTextNodeAtPosition(context, len);
@ -911,6 +918,9 @@ function saveCaretPosition(context: HTMLElement,offset=0){
range.setStart(pos.node, pos.position);
selection.addRange(range);
};
}catch{
return undefined;
}
}
function getTextNodeAtPosition(root: Node, index: number):{

View file

@ -1,7 +1,7 @@
import{ Contextmenu }from"./contextmenu.js";
import{ User }from"./user.js";
import{ Member }from"./member.js";
import{ MarkDown }from"./markdown.js";
import{ MarkDown, saveCaretPosition }from"./markdown.js";
import{ Embed }from"./embed.js";
import{ Channel }from"./channel.js";
import{ Localuser }from"./localuser.js";
@ -96,14 +96,10 @@ class Message extends SnowFlake{
);
}
setEdit(){
const prev=this.channel.editing;
this.channel.editing = this;
const markdown = (
document.getElementById("typebox") as HTMLDivElement & {
markdown: MarkDown;
}
).markdown as MarkDown;
markdown.txt = this.content.rawString.split("");
markdown.boxupdate(document.getElementById("typebox") as HTMLDivElement);
if(prev) prev.generateMessage();
this.generateMessage(undefined,false)
}
constructor(messagejson: messagejson, owner: Channel){
super(messagejson.id);
@ -340,6 +336,7 @@ class Message extends SnowFlake{
}
generateMessage(premessage?: Message | undefined, ignoredblock = false){
if(!this.div)return;
const editmode=this.channel.editing===this;
if(!premessage){
premessage = this.channel.messages.get(
this.channel.idToPrev.get(this.id) as string
@ -476,8 +473,7 @@ class Message extends SnowFlake{
const newt = new Date(this.timestamp).getTime() / 1000;
current = newt - old > 600;
}
const combine =
premessage?.author != this.author || current || this.message_reference;
const combine = premessage?.author != this.author || current || this.message_reference;
if(combine){
const pfp = this.author.buildpfp();
this.author.bind(pfp, this.guild, false);
@ -526,13 +522,56 @@ class Message extends SnowFlake{
}else{
div.classList.remove("topMessage");
}
const messagedwrap = document.createElement("div");
if(editmode){
const box=document.createElement("div");
box.classList.add("messageEditContainer");
const area=document.createElement("div");
const sb=document.createElement("div");
sb.style.position="absolute";
sb.style.width="100%";
const search=document.createElement("div");
search.classList.add("searchOptions","flexttb");
area.classList.add("editMessage");
area.contentEditable="true";
const md=new MarkDown(this.content.rawString,this.owner)
area.append(md.makeHTML());
area.addEventListener("keyup", (event)=>{
if(this.localuser.keyup(event)) return;
if(event.key === "Enter" && !event.shiftKey){
this.edit(md.rawString);
this.channel.editing=null;
this.generateMessage();
}
});
area.addEventListener("keydown", event=>{
this.localuser.keydown(event);
if(event.key === "Enter" && !event.shiftKey) event.preventDefault();
if(event.key === "Escape"){
this.channel.editing=null;
this.generateMessage();
}
});
md.giveBox(area,(str,pre)=>{
this.localuser.search(search,md,str,pre)
})
sb.append(search);
box.append(sb,area);
messagedwrap.append(box);
setTimeout(()=>{
area.focus();
const fun=saveCaretPosition(area,Infinity);
if(fun) fun();
})
}else{
this.content.onUpdate=()=>{};
const messaged = this.content.makeHTML();
(div as any).txt = messaged;
const messagedwrap = document.createElement("div");
messagedwrap.classList.add("flexttb");
messagedwrap.appendChild(messaged);
text.appendChild(messagedwrap);
}
text.appendChild(messagedwrap);
build.appendChild(text);
if(this.attachments.length){
console.log(this.attachments);

View file

@ -21,7 +21,7 @@ body {
display: flex;
flex-direction: column;
}
#searchOptions{
.searchOptions{
padding:.05in .15in;
border-radius: .1in;
background: var(--channels-bg);
@ -46,15 +46,17 @@ body {
span:hover{
background:var(--button-bg);
}
;
margin: 16px;
border: solid .025in var(--black);
}
#searchOptions:empty{
.searchOptions:empty{
padding: 0;
border: 0;
}
.messageEditContainer{
position: relative;
width:100%;
}
.flexgrow {
flex-grow: 1;
min-height: 0;
@ -268,6 +270,11 @@ textarea {
transition: opacity .2s;
border: solid .03in var(--black);
}
.editMessage{
background: var(--typebox-bg);
padding: .05in;
border-radius: .04in;
}
/* Animations */
@keyframes fade {
0%, 100% {
@ -1004,6 +1011,7 @@ span.instanceStatus {
.commentrow {
word-break: break-word;
gap: 4px;
width: 100%;
}
.username {
margin-top: auto;