way improved editing of messages
This commit is contained in:
parent
c1645099b8
commit
ce538b3909
8 changed files with 204 additions and 145 deletions
|
@ -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>{
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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):{
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue