diff --git a/src/webpage/channel.ts b/src/webpage/channel.ts index 4ba0d35..95b6f05 100644 --- a/src/webpage/channel.ts +++ b/src/webpage/channel.ts @@ -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 = new Map(); async typingStart(typing: startTypingjson): Promise{ diff --git a/src/webpage/direct.ts b/src/webpage/direct.ts index 3e41b5e..15dbed0 100644 --- a/src/webpage/direct.ts +++ b/src/webpage/direct.ts @@ -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); diff --git a/src/webpage/index.html b/src/webpage/index.html index f4ac781..46c0197 100644 --- a/src/webpage/index.html +++ b/src/webpage/index.html @@ -75,7 +75,7 @@
-
+
diff --git a/src/webpage/index.ts b/src/webpage/index.ts index 6b1cb43..4ab27cf 100644 --- a/src/webpage/index.ts +++ b/src/webpage/index.ts @@ -153,36 +153,30 @@ 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; - if(replyingTo?.div){ - replyingTo.div.classList.remove("replying"); - } - if(thisUser.channelfocus){ - thisUser.channelfocus.replyingto = null; - } - channel.sendMessage(markdown.rawString, { - attachments: images, - // @ts-ignore This is valid according to the API - embeds: [], // Add an empty array for the embeds property - replyingto: replyingTo, - }); - if(thisUser.channelfocus){ - thisUser.channelfocus.makereplybox(); - } + replyingTo = thisUser.channelfocus? thisUser.channelfocus.replyingto: null; + if(replyingTo?.div){ + replyingTo.div.classList.remove("replying"); + } + if(thisUser.channelfocus){ + thisUser.channelfocus.replyingto = null; + } + channel.sendMessage(markdown.rawString, { + attachments: images, + // @ts-ignore This is valid according to the API + embeds: [], // Add an empty array for the embeds property + replyingto: replyingTo, + }); + if(thisUser.channelfocus){ + thisUser.channelfocus.makereplybox(); } - while(images.length){ images.pop(); pasteImageElement.removeChild(imagesHtml.pop() as HTMLElement); diff --git a/src/webpage/localuser.ts b/src/webpage/localuser.ts index 489acc1..14637fc 100644 --- a/src/webpage/localuser.ts +++ b/src/webpage/localuser.ts @@ -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; diff --git a/src/webpage/markdown.ts b/src/webpage/markdown.ts index 0baa143..13f8167 100644 --- a/src/webpage/markdown.ts +++ b/src/webpage/markdown.ts @@ -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,85 +836,91 @@ let formatted=false; function saveCaretPosition(context: HTMLElement,offset=0){ const selection = window.getSelection() as Selection; if(!selection)return; - const range = selection.getRangeAt(0); - 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; + try{ + const range = selection.getRangeAt(0); + + 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++; + } + if(base instanceof HTMLElement){ + baseString=MarkDown.gatherBoxText(base) + }else{ + baseString=base.textContent as string; } - i++; - } - if(base instanceof HTMLElement){ - baseString=MarkDown.gatherBoxText(base) }else{ - baseString=base.textContent as string; + baseString=selection.toString(); } - }else{ - baseString=selection.toString(); - } - range.setStart(context, 0); + range.setStart(context, 0); - let build=""; - //I think this is working now :3 - function crawlForText(context:Node){ - //@ts-ignore - const children=[...context.childNodes]; - if(children.length===1&&children[0] instanceof Text){ - if(selection.containsNode(context,false)){ - build+=MarkDown.gatherBoxText(context as HTMLElement); - }else if(selection.containsNode(context,true)){ - if(context.contains(base)||context===base||base.contains(context)){ - build+=baseString; + let build=""; + //I think this is working now :3 + function crawlForText(context:Node){ + //@ts-ignore + const children=[...context.childNodes]; + if(children.length===1&&children[0] instanceof Text){ + if(selection.containsNode(context,false)){ + build+=MarkDown.gatherBoxText(context as HTMLElement); + }else if(selection.containsNode(context,true)){ + if(context.contains(base)||context===base||base.contains(context)){ + build+=baseString; + }else{ + build+=context.textContent; + } }else{ - build+=context.textContent; + console.error(context); } - }else{ - console.error(context); + return; } - return; - } - for(const node of children as Node[]){ + for(const node of children as Node[]){ - if(selection.containsNode(node,false)){ - if(node instanceof HTMLElement){ - build+=MarkDown.gatherBoxText(node); + if(selection.containsNode(node,false)){ + if(node instanceof HTMLElement){ + build+=MarkDown.gatherBoxText(node); + }else{ + build+=node.textContent; + } + }else if(selection.containsNode(node,true)){ + if(node instanceof HTMLElement){ + crawlForText(node); + }else{ + console.error(node,"This shouldn't happen") + } }else{ - build+=node.textContent; + //console.error(node,"This shouldn't happen"); } - }else if(selection.containsNode(node,true)){ - if(node instanceof HTMLElement){ - crawlForText(node); - }else{ - console.error(node,"This shouldn't happen") - } - }else{ - console.error(node,"This shouldn't happen"); } } + crawlForText(context); + if(baseString==="\n"){ + build+=baseString; + } + text=build; + let len=build.length+offset; + len=Math.min(len,MarkDown.gatherBoxText(context).length) + return function restore(){ + if(!selection)return; + const pos = getTextNodeAtPosition(context, len); + selection.removeAllRanges(); + const range = new Range(); + range.setStart(pos.node, pos.position); + selection.addRange(range); + }; + }catch{ + return undefined; } - crawlForText(context); - if(baseString==="\n"){ - build+=baseString; - } - text=build; - const len=build.length+offset; - return function restore(){ - if(!selection)return; - const pos = getTextNodeAtPosition(context, len); - selection.removeAllRanges(); - const range = new Range(); - range.setStart(pos.node, pos.position); - selection.addRange(range); - }; } function getTextNodeAtPosition(root: Node, index: number):{ diff --git a/src/webpage/message.ts b/src/webpage/message.ts index 2296c7a..e1dca43 100644 --- a/src/webpage/message.ts +++ b/src/webpage/message.ts @@ -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 messaged = this.content.makeHTML(); - (div as any).txt = messaged; const messagedwrap = document.createElement("div"); - messagedwrap.classList.add("flexttb"); - messagedwrap.appendChild(messaged); - text.appendChild(messagedwrap); + 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; + messagedwrap.classList.add("flexttb"); + messagedwrap.appendChild(messaged); + } + text.appendChild(messagedwrap); build.appendChild(text); if(this.attachments.length){ console.log(this.attachments); diff --git a/src/webpage/style.css b/src/webpage/style.css index 46fcff0..b34ad09 100644 --- a/src/webpage/style.css +++ b/src/webpage/style.css @@ -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;