import { Channel } from "./channel.js"; import { Emoji } from "./emoji.js"; import { Localuser } from "./localuser.js"; export {MarkDown}; class MarkDown{ txt : string[]; keep:boolean; stdsize:boolean; owner:Localuser|Channel; info:Localuser["info"]; constructor(text : string|string[],owner:MarkDown["owner"],{keep=false,stdsize=false} = {}){ if((typeof text)===(typeof "")){ this.txt=(text as string).split(""); }else{ this.txt=(text as string[]); } if(this.txt===undefined){ this.txt=[]; } this.info=owner.info; this.keep=keep; this.owner=owner; this.stdsize=stdsize; } get rawString(){ return this.txt.join(""); } get textContent(){ return this.makeHTML().textContent; } makeHTML({keep=this.keep,stdsize=this.stdsize}={}){ return this.markdown(this.txt,{keep:keep,stdsize:stdsize}); } markdown(text : string|string[],{keep=false,stdsize=false} = {}){ let txt : string[]; if((typeof text)===(typeof "")){ txt=(text as string).split(""); }else{ txt=(text as string[]); } if(txt===undefined){ txt=[]; } const span=document.createElement("span"); let current=document.createElement("span"); function appendcurrent(){ if(current.innerHTML!==""){ span.append(current); current=document.createElement("span"); } } for(let i=0;i"&&txt[i+2]===" "){ element=document.createElement("div"); const line=document.createElement("div"); line.classList.add("quoteline"); element.append(line); element.classList.add("quote"); keepys="> "; i+=3; } if(keepys){ appendcurrent(); if(!first&&!stdsize){ span.appendChild(document.createElement("br")); } const build=[]; for(;txt[i]!=="\n"&&txt[i]!==undefined;i++){ build.push(txt[i]); } if(stdsize){ element=document.createElement("span"); } if(keep){ element.append(keepys); } element.appendChild(this.markdown(build,{keep:keep,stdsize:stdsize})); span.append(element); i-=1; console.log(txt[i]); continue; } if(first){ i++; } } if(txt[i]==="\n"){ if(!stdsize){ appendcurrent(); span.append(document.createElement("br")); } continue; } if(txt[i]==="`"){ let count=1; if(txt[i+1]==="`"){ count++; if(txt[i+2]==="`"){ count++; } } let build=""; if(keep){ build+="`".repeat(count); } let find=0; let j=i+count; let init=true; for(;txt[j]!==undefined&&(txt[j]!=="\n"||count===3)&&find!==count;j++){ if(txt[j]==="`"){ find++; }else{ if(find!==0){ build+="`".repeat(find); find=0; } if(init&&count===3){ if(txt[j]===" "||txt[j]==="\n"){ init=false; } if(keep){ build+=txt[j]; } continue; } build+=txt[j]; } } if(stdsize){ console.log(build); build=build.replaceAll("\n",""); console.log(build,JSON.stringify(build)); } if(find===count){ appendcurrent(); i=j; if(keep){ build+="`".repeat(find); } if(count!==3&&!stdsize){ const samp=document.createElement("samp"); samp.textContent=build; span.appendChild(samp); }else{ const pre=document.createElement("pre"); if(build[build.length-1]==="\n"){ build=build.substring(0,build.length-1); } if(txt[i]==="\n"){ i++ } pre.textContent=build; span.appendChild(pre); } i--; continue; } } if(txt[i]==="*"){ let count=1; if(txt[i+1]==="*"){ count++; if(txt[i+2]==="*"){ count++; } } let build=[]; let find=0; let j=i+count; for(;txt[j]!==undefined&&find!==count;j++){ if(txt[j]==="*"){ find++; }else{ build.push(txt[j]); if(find!==0){ build=build.concat(new Array(find).fill("*")); find=0; } } } if(find===count&&(count!=1||txt[i+1]!==" ")){ appendcurrent(); i=j; const stars="*".repeat(count); if(count===1){ const i=document.createElement("i"); if(keep){i.append(stars)} i.appendChild(this.markdown(build,{keep:keep,stdsize:stdsize})); if(keep){i.append(stars)} span.appendChild(i); }else if(count===2){ const b=document.createElement("b"); if(keep){b.append(stars)} b.appendChild(this.markdown(build,{keep:keep,stdsize:stdsize})); if(keep){b.append(stars)} span.appendChild(b); }else{ const b=document.createElement("b"); const i=document.createElement("i"); if(keep){b.append(stars)} b.appendChild(this.markdown(build,{keep:keep,stdsize:stdsize})); if(keep){b.append(stars)} i.appendChild(b); span.appendChild(i); } i-- continue; } } if(txt[i]==="_"){ let count=1; if(txt[i+1]==="_"){ count++; if(txt[i+2]==="_"){ count++; } } let build=[]; let find=0; let j=i+count; for(;txt[j]!==undefined&&find!==count;j++){ if(txt[j]==="_"){ find++; }else{ build.push(txt[j]); if(find!==0){ build=build.concat(new Array(find).fill("_")); find=0; } } } if(find===count&&(count!=1||(txt[j+1]===" "||txt[j+1]==="\n"||txt[j+1]===undefined))){ appendcurrent(); i=j; const underscores="_".repeat(count); if(count===1){ const i=document.createElement("i"); if(keep){i.append(underscores)} i.appendChild(this.markdown(build,{keep:keep,stdsize:stdsize})); if(keep){i.append(underscores)} span.appendChild(i); }else if(count===2){ const u=document.createElement("u"); if(keep){u.append(underscores)} u.appendChild(this.markdown(build,{keep:keep,stdsize:stdsize})); if(keep){u.append(underscores)} span.appendChild(u); }else{ const u=document.createElement("u"); const i=document.createElement("i"); if(keep){i.append(underscores)} i.appendChild(this.markdown(build,{keep:keep,stdsize:stdsize})); if(keep){i.append(underscores)} u.appendChild(i) span.appendChild(u); } i--; continue; } } if(txt[i]==="~"&&txt[i+1]==="~"){ let count=2; let build=[]; let find=0; let j=i+2; for(;txt[j]!==undefined&&find!==count;j++){ if(txt[j]==="~"){ find++; }else{ build.push(txt[j]); if(find!==0){ build=build.concat(new Array(find).fill("~")); find=0; } } } if(find===count){ appendcurrent(); i=j-1; const tildes="~~"; if(count===2){ const s=document.createElement("s"); if(keep){s.append(tildes)} s.appendChild(this.markdown(build,{keep:keep,stdsize:stdsize})); if(keep){s.append(tildes)} span.appendChild(s); } continue; } } if(txt[i]==="|"&&txt[i+1]==="|"){ let count=2; let build=[]; let find=0; let j=i+2; for(;txt[j]!==undefined&&find!==count;j++){ if(txt[j]==="|"){ find++; }else{ build.push(txt[j]); if(find!==0){ build=build.concat(new Array(find).fill("~")); find=0; } } } if(find===count){ appendcurrent(); i=j-1; const pipes="||"; if(count===2){ const j=document.createElement("j"); if(keep){j.append(pipes)} j.appendChild(this.markdown(build,{keep:keep,stdsize:stdsize})); j.classList.add("spoiler"); j.onclick=MarkDown.unspoil; if(keep){j.append(pipes)} span.appendChild(j); } continue; } } if (txt[i]==="<" && txt[i + 1]==="t" && txt[i + 2]===":") { let found=false; const build=["<","t",":"]; let j = i+3; for (; txt[j] !== void 0; j++) { build.push(txt[j]); if (txt[j]===">") { found=true; break; } } if (found) { appendcurrent(); i=j; const parts=build.join("").match(/^$/); const dateInput=new Date(Number.parseInt(parts[1]) * 1000); let time=""; if (Number.isNaN(dateInput.getTime())) time=build.join(""); else { if (parts[3]==="d") time=dateInput.toLocaleString(void 0, {day: "2-digit", month: "2-digit", year: "numeric"}); else if (parts[3]==="D") time=dateInput.toLocaleString(void 0, {day: "numeric", month: "long", year: "numeric"}); else if (!parts[3] || parts[3]==="f") time=dateInput.toLocaleString(void 0, {day: "numeric", month: "long", year: "numeric"}) + " " + dateInput.toLocaleString(void 0, {hour: "2-digit", minute: "2-digit"}); else if (parts[3]==="F") time=dateInput.toLocaleString(void 0, {day: "numeric", month: "long", year: "numeric", weekday: "long"}) + " " + dateInput.toLocaleString(void 0, {hour: "2-digit", minute: "2-digit"}); else if (parts[3]==="t") time=dateInput.toLocaleString(void 0, {hour: "2-digit", minute: "2-digit"}); else if (parts[3]==="T") time=dateInput.toLocaleString(void 0, {hour: "2-digit", minute: "2-digit", second: "2-digit"}); else if (parts[3]==="R") time=Math.round((Date.now() - (Number.parseInt(parts[1]) * 1000))/1000/60) + " minutes ago"; } const timeElem=document.createElement("span"); timeElem.classList.add("markdown-timestamp"); timeElem.textContent=time; span.appendChild(timeElem); continue; } } if (txt[i] === "<" && (txt[i + 1] === ":" || (txt[i + 1] === "a" && txt[i + 2] === ":"))) { let found=false; const build = txt[i + 1] === "a" ? ["<","a",":"] : ["<",":"]; let j = i+build.length; for (; txt[j] !== void 0; j++) { build.push(txt[j]); if (txt[j]===">") { found=true; break; } } if (found) { const buildjoin=build.join(""); const parts=buildjoin.match(/^<(a)?:\w+:(\d{10,30})>$/); if (parts && parts[2]) { appendcurrent(); i=j; const isEmojiOnly = txt.join("").trim()===buildjoin.trim(); const owner=(this.owner instanceof Channel)?this.owner.guild:this.owner const emoji=new Emoji({name:buildjoin,id:parts[2],animated:!!parts[1]},owner); span.appendChild(emoji.getHTML(isEmojiOnly)); continue; } } } current.textContent+=txt[i]; } appendcurrent(); return span; } static unspoil(e:any) : void{ e.target.classList.remove("spoiler"); e.target.classList.add("unspoiled"); } giveBox(box:HTMLDivElement){ box.onkeydown=_=>{ //console.log(_); }; let prevcontent=""; box.onkeyup=_=>{ const content=MarkDown.gatherBoxText(box); if(content!==prevcontent){ prevcontent=content; this.txt=content.split(""); this.boxupdate(box); } }; box.onpaste=_=>{ console.log(_.clipboardData.types) const data=_.clipboardData.getData("text"); document.execCommand('insertHTML', false, data); _.preventDefault(); box.onkeyup(new KeyboardEvent("_")) } } boxupdate(box:HTMLElement){ var restore = saveCaretPosition(box); box.innerHTML=""; box.append(this.makeHTML({keep:true})) restore(); } static gatherBoxText(element:HTMLElement){ if(element.tagName.toLowerCase()==="img"){ return (element as HTMLImageElement).alt; } if(element.tagName.toLowerCase()==="br"){ return "\n"; } let build=""; for(const thing of element.childNodes){ if(thing instanceof Text){ const text=thing.textContent; build+=text; continue; } const text=this.gatherBoxText(thing as HTMLElement); if(text){ build+=text; } } return build; } } //solution from https://stackoverflow.com/questions/4576694/saving-and-restoring-caret-position-for-contenteditable-div function saveCaretPosition(context){ var selection = window.getSelection(); var range = selection.getRangeAt(0); range.setStart( context, 0 ); var len = range.toString().length; return function restore(){ var pos = getTextNodeAtPosition(context, len); selection.removeAllRanges(); var range = new Range(); range.setStart(pos.node ,pos.position); selection.addRange(range); } } function getTextNodeAtPosition(root, index){ const NODE_TYPE = NodeFilter.SHOW_TEXT; var treeWalker = document.createTreeWalker(root, NODE_TYPE, function next(elem) { if(index > elem.textContent.length){ index -= elem.textContent.length; return NodeFilter.FILTER_REJECT } return NodeFilter.FILTER_ACCEPT; }); var c = treeWalker.nextNode(); return { node: c? c: root, position: index }; }