mention/channel autofil
This commit is contained in:
parent
26ac410da9
commit
4a64972cd1
9 changed files with 438 additions and 46 deletions
|
@ -856,6 +856,16 @@ class Channel extends SnowFlake{
|
||||||
setTimeout(this.rendertyping.bind(this), 10000);
|
setTimeout(this.rendertyping.bind(this), 10000);
|
||||||
this.rendertyping();
|
this.rendertyping();
|
||||||
}
|
}
|
||||||
|
similar(str:string){
|
||||||
|
if(this.type===4) return -1;
|
||||||
|
const strl=Math.max(str.length,1)
|
||||||
|
if(this.name.includes(str)){
|
||||||
|
return strl/this.name.length;
|
||||||
|
}else if(this.name.toLowerCase().includes(str.toLowerCase())){
|
||||||
|
return strl/this.name.length/1.2;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
rendertyping(): void{
|
rendertyping(): void{
|
||||||
const typingtext = document.getElementById("typing") as HTMLDivElement;
|
const typingtext = document.getElementById("typing") as HTMLDivElement;
|
||||||
let build = "";
|
let build = "";
|
||||||
|
|
|
@ -74,6 +74,9 @@
|
||||||
<div id="loadingdiv">
|
<div id="loadingdiv">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div style="position: relative;">
|
||||||
|
<div id="searchOptions" class="flexttb"></div>
|
||||||
|
</div>
|
||||||
<div id="pasteimage" class="flexltr"></div>
|
<div id="pasteimage" class="flexltr"></div>
|
||||||
<div id="replybox" class="hideReplyBox"></div>
|
<div id="replybox" class="hideReplyBox"></div>
|
||||||
<div id="typediv">
|
<div id="typediv">
|
||||||
|
|
|
@ -157,6 +157,7 @@ import { I18n } from "./i18n.js";
|
||||||
let replyingTo: Message | null = null;
|
let replyingTo: Message | null = null;
|
||||||
|
|
||||||
async function handleEnter(event: KeyboardEvent): Promise<void>{
|
async function handleEnter(event: KeyboardEvent): Promise<void>{
|
||||||
|
if(thisUser.keyup(event)){return}
|
||||||
const channel = thisUser.channelfocus;
|
const channel = thisUser.channelfocus;
|
||||||
if(!channel)return;
|
if(!channel)return;
|
||||||
|
|
||||||
|
@ -198,15 +199,14 @@ import { I18n } from "./i18n.js";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CustomHTMLDivElement extends HTMLDivElement {
|
interface CustomHTMLDivElement extends HTMLDivElement {markdown: MarkDown;}
|
||||||
markdown: MarkDown;
|
|
||||||
}
|
|
||||||
|
|
||||||
const typebox = document.getElementById("typebox") as CustomHTMLDivElement;
|
const typebox = document.getElementById("typebox") as CustomHTMLDivElement;
|
||||||
const markdown = new MarkDown("", thisUser);
|
const markdown = new MarkDown("", thisUser);
|
||||||
typebox.markdown = markdown;
|
typebox.markdown = markdown;
|
||||||
typebox.addEventListener("keyup", handleEnter);
|
typebox.addEventListener("keyup", handleEnter);
|
||||||
typebox.addEventListener("keydown", event=>{
|
typebox.addEventListener("keydown", event=>{
|
||||||
|
thisUser.keydown(event)
|
||||||
if(event.key === "Enter" && !event.shiftKey) event.preventDefault();
|
if(event.key === "Enter" && !event.shiftKey) event.preventDefault();
|
||||||
});
|
});
|
||||||
markdown.giveBox(typebox);
|
markdown.giveBox(typebox);
|
||||||
|
|
|
@ -5,10 +5,10 @@ import{ AVoice }from"./audio.js";
|
||||||
import{ User }from"./user.js";
|
import{ User }from"./user.js";
|
||||||
import{ Dialog }from"./dialog.js";
|
import{ Dialog }from"./dialog.js";
|
||||||
import{ getapiurls, getBulkInfo, setTheme, Specialuser, SW }from"./login.js";
|
import{ getapiurls, getBulkInfo, setTheme, Specialuser, SW }from"./login.js";
|
||||||
import{channeljson,guildjson,mainuserjson,memberjson,memberlistupdatejson,messageCreateJson,presencejson,readyjson,startTypingjson,wsjson,}from"./jsontypes.js";
|
import{channeljson,guildjson,mainuserjson,memberjson,memberlistupdatejson,messageCreateJson,presencejson,readyjson,startTypingjson,userjson,wsjson,}from"./jsontypes.js";
|
||||||
import{ Member }from"./member.js";
|
import{ Member }from"./member.js";
|
||||||
import{ Form, FormError, Options, Settings }from"./settings.js";
|
import{ Form, FormError, Options, Settings }from"./settings.js";
|
||||||
import{ MarkDown }from"./markdown.js";
|
import{ getTextNodeAtPosition, MarkDown, saveCaretPosition }from"./markdown.js";
|
||||||
import { Bot } from "./bot.js";
|
import { Bot } from "./bot.js";
|
||||||
import { Role } from "./role.js";
|
import { Role } from "./role.js";
|
||||||
import { VoiceFactory } from "./voice.js";
|
import { VoiceFactory } from "./voice.js";
|
||||||
|
@ -67,6 +67,7 @@ class Localuser{
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
async gottenReady(ready: readyjson): Promise<void>{
|
async gottenReady(ready: readyjson): Promise<void>{
|
||||||
|
|
||||||
await I18n.done;
|
await I18n.done;
|
||||||
this.initialized = true;
|
this.initialized = true;
|
||||||
this.ready = ready;
|
this.ready = ready;
|
||||||
|
@ -77,6 +78,8 @@ class Localuser{
|
||||||
this.resume_gateway_url=ready.d.resume_gateway_url;
|
this.resume_gateway_url=ready.d.resume_gateway_url;
|
||||||
this.session_id=ready.d.session_id;
|
this.session_id=ready.d.session_id;
|
||||||
|
|
||||||
|
this.mdBox();
|
||||||
|
|
||||||
this.voiceFactory=new VoiceFactory({id:this.user.id});
|
this.voiceFactory=new VoiceFactory({id:this.user.id});
|
||||||
this.handleVoice();
|
this.handleVoice();
|
||||||
this.mfa_enabled = ready.d.user.mfa_enabled as boolean;
|
this.mfa_enabled = ready.d.user.mfa_enabled as boolean;
|
||||||
|
@ -1711,11 +1714,226 @@ class Localuser{
|
||||||
Bot.InviteMaker(appId,form,this.info);
|
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.onUpdate=this.search.bind(this);
|
||||||
|
}
|
||||||
|
MDReplace(replacewith:string,original:string){
|
||||||
|
const typebox = document.getElementById("typebox") as HTMLDivElement;
|
||||||
|
if(!this.typeMd)return;
|
||||||
|
let raw=this.typeMd.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("");
|
||||||
|
this.typeMd.boxupdate(typebox);
|
||||||
|
}
|
||||||
|
MDSearchOptions(options:[string,string][],original:string){
|
||||||
|
console.warn(original);
|
||||||
|
const div=document.getElementById("searchOptions");
|
||||||
|
if(!div)return;
|
||||||
|
div.innerHTML="";
|
||||||
|
let i=0;
|
||||||
|
const htmloptions:HTMLSpanElement[]=[];
|
||||||
|
for(const thing of options){
|
||||||
|
if(i==8){
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
const span=document.createElement("span");
|
||||||
|
htmloptions.push(span);
|
||||||
|
span.textContent=thing[0];
|
||||||
|
span.onclick=(e)=>{
|
||||||
|
|
||||||
|
if(e){
|
||||||
|
const selection = window.getSelection() as Selection;
|
||||||
|
const typebox = document.getElementById("typebox") as HTMLDivElement;
|
||||||
|
if(selection){
|
||||||
|
console.warn(original);
|
||||||
|
const pos = getTextNodeAtPosition(typebox, 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();
|
||||||
|
}
|
||||||
|
this.MDReplace(thing[1],original);
|
||||||
|
div.innerHTML="";
|
||||||
|
remove();
|
||||||
|
}
|
||||||
|
div.prepend(span);
|
||||||
|
}
|
||||||
|
const remove=()=>{
|
||||||
|
if(div&&div.innerHTML===""){
|
||||||
|
this.keyup=()=>false;
|
||||||
|
this.keydown=()=>{};
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if(htmloptions[0]){
|
||||||
|
let curindex=0;
|
||||||
|
let cur=htmloptions[0];
|
||||||
|
cur.classList.add("selected");
|
||||||
|
const cancel=new Set(["ArrowUp","ArrowDown","Enter","Tab"]);
|
||||||
|
this.keyup=(event)=>{
|
||||||
|
if(remove()) return false;
|
||||||
|
if(cancel.has(event.key)){
|
||||||
|
switch(event.key){
|
||||||
|
case "ArrowUp":
|
||||||
|
if(htmloptions[curindex+1]){
|
||||||
|
cur.classList.remove("selected");
|
||||||
|
curindex++;
|
||||||
|
cur=htmloptions[curindex];
|
||||||
|
cur.classList.add("selected");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "ArrowDown":
|
||||||
|
if(htmloptions[curindex-1]){
|
||||||
|
cur.classList.remove("selected");
|
||||||
|
curindex--;
|
||||||
|
cur=htmloptions[curindex];
|
||||||
|
cur.classList.add("selected");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "Enter":
|
||||||
|
case "Tab":
|
||||||
|
//@ts-ignore
|
||||||
|
cur.onclick();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
this.keydown=(event)=>{
|
||||||
|
if(remove()) return;
|
||||||
|
if(cancel.has(event.key)){
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MDFindChannel(name:string,orginal:string){
|
||||||
|
const maybe:[number,Channel][]=[];
|
||||||
|
if(this.lookingguild&&this.lookingguild.id!=="@me"){
|
||||||
|
for(const channel of this.lookingguild.channels){
|
||||||
|
const confidence=channel.similar(name);
|
||||||
|
if(confidence>0){
|
||||||
|
maybe.push([confidence,channel]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
maybe.sort((a,b)=>b[0]-a[0]);
|
||||||
|
this.MDSearchOptions(maybe.map((a)=>["# "+a[1].name,`<#${a[1].id}> `]),orginal);
|
||||||
|
}
|
||||||
|
async getUser(id:string){
|
||||||
|
if(this.userMap.has(id)){
|
||||||
|
return this.userMap.get(id) as User;
|
||||||
|
}
|
||||||
|
return new User(await (await fetch(this.info.api+"/users/"+id)).json(),this);
|
||||||
|
}
|
||||||
|
MDFineMentionGen(name:string,original:string){
|
||||||
|
let members:[Member,number][]=[];
|
||||||
|
if(this.lookingguild){
|
||||||
|
for(const member of this.lookingguild.members){
|
||||||
|
const rank=member.compare(name);
|
||||||
|
if(rank>0){
|
||||||
|
members.push([member,rank])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
members.sort((a,b)=>a[1]-b[1]);
|
||||||
|
console.log(members);
|
||||||
|
this.MDSearchOptions(members.map((a)=>["@"+a[0].name,`<@${a[0].id}> `]),original);
|
||||||
|
}
|
||||||
|
MDFindMention(name:string,original:string){
|
||||||
|
console.log(original);
|
||||||
|
if(this.ws&&this.lookingguild){
|
||||||
|
this.MDFineMentionGen(name,original);
|
||||||
|
const nonce=Math.floor(Math.random()*10**8)+"";
|
||||||
|
if(this.lookingguild.member_count<=this.lookingguild.members.size) return;
|
||||||
|
this.ws.send(JSON.stringify(
|
||||||
|
{op:8,
|
||||||
|
d:{
|
||||||
|
guild_id:[this.lookingguild.id],
|
||||||
|
query:name,
|
||||||
|
limit:8,
|
||||||
|
presences:true,
|
||||||
|
nonce
|
||||||
|
}
|
||||||
|
}
|
||||||
|
));
|
||||||
|
this.searchMap.set(nonce,async (e)=>{
|
||||||
|
console.log(e);
|
||||||
|
if(e.members&&e.members[0]){
|
||||||
|
if(e.members[0].user){
|
||||||
|
for(const thing of e.members){
|
||||||
|
await Member.new(thing,this.lookingguild as Guild)
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
const prom1:Promise<User>[]=[];
|
||||||
|
for(const thing of e.members){
|
||||||
|
prom1.push(this.getUser(thing.id));
|
||||||
|
}
|
||||||
|
Promise.all(prom1);
|
||||||
|
for(const thing of e.members){
|
||||||
|
if(!this.userMap.has(thing.id)){
|
||||||
|
console.warn("Dumb server bug for this member",thing);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
await Member.new(thing,this.lookingguild as Guild)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.MDFineMentionGen(name,original);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
search(str:string,pre:boolean){
|
||||||
|
if(!pre){
|
||||||
|
const match=str.match(this.autofillregex);
|
||||||
|
|
||||||
|
if(match){
|
||||||
|
console.log(str,match);
|
||||||
|
const [type, search]=[match[0][0],match[0].split(/@|#|:/)[1]];
|
||||||
|
console.log(type,search);
|
||||||
|
switch(type){
|
||||||
|
case "#":
|
||||||
|
this.MDFindChannel(search,str);
|
||||||
|
break;
|
||||||
|
case "@":
|
||||||
|
this.MDFindMention(search,str);
|
||||||
|
break;
|
||||||
|
case ":":
|
||||||
|
if(search.length>=2){
|
||||||
|
console.log("implement me");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const div=document.getElementById("searchOptions");
|
||||||
|
if(!div)return;
|
||||||
|
div.innerHTML="";
|
||||||
|
}
|
||||||
|
keydown:(event:KeyboardEvent)=>unknown=()=>{};
|
||||||
|
keyup:(event:KeyboardEvent)=>boolean=()=>false;
|
||||||
//---------- resolving members code -----------
|
//---------- resolving members code -----------
|
||||||
readonly waitingmembers: Map<
|
readonly waitingmembers = new Map<string,Map<string, (returns: memberjson | undefined) => void>>();
|
||||||
string,
|
|
||||||
Map<string, (returns: memberjson | undefined) => void>
|
|
||||||
> = new Map();
|
|
||||||
readonly presences: Map<string, presencejson> = new Map();
|
readonly presences: Map<string, presencejson> = new Map();
|
||||||
async resolvemember(
|
async resolvemember(
|
||||||
id: string,
|
id: string,
|
||||||
|
@ -1757,6 +1975,14 @@ class Localuser{
|
||||||
fetchingmembers: Map<string, boolean> = new Map();
|
fetchingmembers: Map<string, boolean> = new Map();
|
||||||
noncemap: Map<string, (r: [memberjson[], string[]]) => void> = new Map();
|
noncemap: Map<string, (r: [memberjson[], string[]]) => void> = new Map();
|
||||||
noncebuild: Map<string, [memberjson[], string[], number[]]> = new Map();
|
noncebuild: Map<string, [memberjson[], string[], number[]]> = new Map();
|
||||||
|
searchMap=new Map<string,(arg:{
|
||||||
|
chunk_index: number,
|
||||||
|
chunk_count: number,
|
||||||
|
nonce: string,
|
||||||
|
not_found?: string[],
|
||||||
|
members?: memberjson[],
|
||||||
|
presences: presencejson[],
|
||||||
|
})=>unknown>();
|
||||||
async gotChunk(chunk: {
|
async gotChunk(chunk: {
|
||||||
chunk_index: number;
|
chunk_index: number;
|
||||||
chunk_count: number;
|
chunk_count: number;
|
||||||
|
@ -1770,6 +1996,14 @@ class Localuser{
|
||||||
this.presences.set(thing.user.id, thing);
|
this.presences.set(thing.user.id, thing);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if(this.searchMap.has(chunk.nonce)){
|
||||||
|
const func=this.searchMap.get(chunk.nonce);
|
||||||
|
this.searchMap.delete(chunk.nonce);
|
||||||
|
if(func){
|
||||||
|
func(chunk);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
chunk.members ??= [];
|
chunk.members ??= [];
|
||||||
const arr = this.noncebuild.get(chunk.nonce);
|
const arr = this.noncebuild.get(chunk.nonce);
|
||||||
if(!arr)return;
|
if(!arr)return;
|
||||||
|
|
|
@ -687,7 +687,9 @@ txt[j + 1] === undefined)
|
||||||
e.target.classList.remove("spoiler");
|
e.target.classList.remove("spoiler");
|
||||||
e.target.classList.add("unspoiled");
|
e.target.classList.add("unspoiled");
|
||||||
}
|
}
|
||||||
giveBox(box: HTMLDivElement){
|
onUpdate:(upto:string,pre:boolean)=>unknown=()=>{};
|
||||||
|
giveBox(box: HTMLDivElement,onUpdate:(upto:string,pre:boolean)=>unknown=()=>{}){
|
||||||
|
this.onUpdate=onUpdate;
|
||||||
box.onkeydown = _=>{
|
box.onkeydown = _=>{
|
||||||
//console.log(_);
|
//console.log(_);
|
||||||
};
|
};
|
||||||
|
@ -698,7 +700,9 @@ txt[j + 1] === undefined)
|
||||||
prevcontent = content;
|
prevcontent = content;
|
||||||
this.txt = content.split("");
|
this.txt = content.split("");
|
||||||
this.boxupdate(box);
|
this.boxupdate(box);
|
||||||
|
MarkDown.gatherBoxText(box);
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
box.onpaste = _=>{
|
box.onpaste = _=>{
|
||||||
if(!_.clipboardData)return;
|
if(!_.clipboardData)return;
|
||||||
|
@ -717,7 +721,10 @@ txt[j + 1] === undefined)
|
||||||
box.append(this.makeHTML({ keep: true }));
|
box.append(this.makeHTML({ keep: true }));
|
||||||
if(restore){
|
if(restore){
|
||||||
restore();
|
restore();
|
||||||
|
const test=saveCaretPosition(box);
|
||||||
|
if(test) test();
|
||||||
}
|
}
|
||||||
|
this.onUpdate(text,formatted);
|
||||||
}
|
}
|
||||||
static gatherBoxText(element: HTMLElement): string{
|
static gatherBoxText(element: HTMLElement): string{
|
||||||
if(element.tagName.toLowerCase() === "img"){
|
if(element.tagName.toLowerCase() === "img"){
|
||||||
|
@ -729,11 +736,17 @@ txt[j + 1] === undefined)
|
||||||
if(element.hasAttribute("real")){
|
if(element.hasAttribute("real")){
|
||||||
return element.getAttribute("real") as string;
|
return element.getAttribute("real") as string;
|
||||||
}
|
}
|
||||||
|
if(element.tagName.toLowerCase() === "pre"||element.tagName.toLowerCase() === "samp"){
|
||||||
|
formatted=true;
|
||||||
|
}else{
|
||||||
|
formatted=false;
|
||||||
|
}
|
||||||
let build = "";
|
let build = "";
|
||||||
for(const thing of Array.from(element.childNodes)){
|
for(const thing of Array.from(element.childNodes)){
|
||||||
if(thing instanceof Text){
|
if(thing instanceof Text){
|
||||||
const text = thing.textContent;
|
const text = thing.textContent;
|
||||||
build += text;
|
build += text;
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const text = this.gatherBoxText(thing as HTMLElement);
|
const text = this.gatherBoxText(thing as HTMLElement);
|
||||||
|
@ -747,10 +760,7 @@ txt[j + 1] === undefined)
|
||||||
static safeLink(elm: HTMLElement, url: string){
|
static safeLink(elm: HTMLElement, url: string){
|
||||||
if(URL.canParse(url)){
|
if(URL.canParse(url)){
|
||||||
const Url = new URL(url);
|
const Url = new URL(url);
|
||||||
if(
|
if(elm instanceof HTMLAnchorElement && this.trustedDomains.has(Url.host)){
|
||||||
elm instanceof HTMLAnchorElement &&
|
|
||||||
this.trustedDomains.has(Url.host)
|
|
||||||
){
|
|
||||||
elm.href = url;
|
elm.href = url;
|
||||||
elm.target = "_blank";
|
elm.target = "_blank";
|
||||||
return;
|
return;
|
||||||
|
@ -820,20 +830,81 @@ txt[j + 1] === undefined)
|
||||||
|
|
||||||
//solution from https://stackoverflow.com/questions/4576694/saving-and-restoring-caret-position-for-contenteditable-div
|
//solution from https://stackoverflow.com/questions/4576694/saving-and-restoring-caret-position-for-contenteditable-div
|
||||||
let text = "";
|
let text = "";
|
||||||
function saveCaretPosition(context: Node){
|
let formatted=false;
|
||||||
const selection = window.getSelection();
|
function saveCaretPosition(context: HTMLElement){
|
||||||
|
const selection = window.getSelection() as Selection;
|
||||||
if(!selection)return;
|
if(!selection)return;
|
||||||
const range = selection.getRangeAt(0);
|
const range = selection.getRangeAt(0);
|
||||||
range.setStart(context, 0);
|
let base=selection.anchorNode as Node;
|
||||||
text = selection.toString();
|
range.setStart(base, 0);
|
||||||
let len = text.length + 1;
|
let baseString:string;
|
||||||
for(const str in text.split("\n")){
|
if(!(base instanceof Text)){
|
||||||
if(str.length !== 0){
|
let i=0;
|
||||||
len--;
|
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;
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
baseString=selection.toString();
|
||||||
}
|
}
|
||||||
len += Number(text.at(-1) === "\n");
|
|
||||||
|
|
||||||
|
|
||||||
|
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;
|
||||||
|
}else{
|
||||||
|
build+=context.textContent;
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
console.error(context);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for(const node of children as 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{
|
||||||
|
console.error(node,"This shouldn't happen");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
crawlForText(context);
|
||||||
|
if(baseString==="\n"){
|
||||||
|
build+=baseString;
|
||||||
|
}
|
||||||
|
text=build;
|
||||||
|
const len=build.length;
|
||||||
return function restore(){
|
return function restore(){
|
||||||
if(!selection)return;
|
if(!selection)return;
|
||||||
const pos = getTextNodeAtPosition(context, len);
|
const pos = getTextNodeAtPosition(context, len);
|
||||||
|
@ -844,20 +915,49 @@ function saveCaretPosition(context: Node){
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function getTextNodeAtPosition(root: Node, index: number){
|
function getTextNodeAtPosition(root: Node, index: number):{
|
||||||
const NODE_TYPE = NodeFilter.SHOW_TEXT;
|
node: Node,
|
||||||
const treeWalker = document.createTreeWalker(root, NODE_TYPE, elem=>{
|
position: number,
|
||||||
if(!elem.textContent)return 0;
|
}{
|
||||||
if(index > elem.textContent.length){
|
if(root instanceof Text){
|
||||||
index -= elem.textContent.length;
|
|
||||||
return NodeFilter.FILTER_REJECT;
|
|
||||||
}
|
|
||||||
return NodeFilter.FILTER_ACCEPT;
|
|
||||||
});
|
|
||||||
const c = treeWalker.nextNode();
|
|
||||||
return{
|
return{
|
||||||
node: c ? c : root,
|
node: root,
|
||||||
position: index,
|
position: index,
|
||||||
};
|
};
|
||||||
|
}else if(root instanceof HTMLBRElement){
|
||||||
|
return{
|
||||||
|
node: root,
|
||||||
|
position: 0,
|
||||||
|
};
|
||||||
|
}else if(root instanceof HTMLElement&&root.hasAttribute("real")){
|
||||||
|
return{
|
||||||
|
node: root,
|
||||||
|
position: -1,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
export{ MarkDown };
|
for(const node of root.childNodes as unknown as Node[]){
|
||||||
|
let len:number
|
||||||
|
if(node instanceof HTMLElement){
|
||||||
|
len=MarkDown.gatherBoxText(node).length;
|
||||||
|
}else{
|
||||||
|
len=(node.textContent as string).length
|
||||||
|
}
|
||||||
|
if(len<index){
|
||||||
|
index-=len;
|
||||||
|
}else{
|
||||||
|
const returny=getTextNodeAtPosition(node,index);
|
||||||
|
if(returny.position===-1){
|
||||||
|
index=0;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return returny;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const span=document.createElement("span");
|
||||||
|
root.appendChild(span)
|
||||||
|
return{
|
||||||
|
node: span,
|
||||||
|
position: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
export{ MarkDown , saveCaretPosition, getTextNodeAtPosition};
|
||||||
|
|
|
@ -124,6 +124,19 @@ class Member extends SnowFlake{
|
||||||
return memb;
|
return memb;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
compare(str:string){
|
||||||
|
function similar(str2:string|null|undefined){
|
||||||
|
if(!str2) return 0;
|
||||||
|
const strl=Math.max(str.length,1)
|
||||||
|
if(str2.includes(str)){
|
||||||
|
return strl/str2.length;
|
||||||
|
}else if(str2.toLowerCase().includes(str.toLowerCase())){
|
||||||
|
return strl/str2.length/1.2;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return Math.max(similar(this.user.name),similar(this.user.nickname),similar(this.nick),similar(this.user.username))
|
||||||
|
}
|
||||||
static async resolveMember(
|
static async resolveMember(
|
||||||
user: User,
|
user: User,
|
||||||
guild: Guild
|
guild: Guild
|
||||||
|
|
|
@ -74,7 +74,7 @@ class Message extends SnowFlake{
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
Message.contextmenu.addbutton(
|
Message.contextmenu.addbutton(
|
||||||
()=>I18n.getTranslation("message.delete"),
|
()=>I18n.getTranslation("message.edit"),
|
||||||
function(this: Message){
|
function(this: Message){
|
||||||
this.setEdit();
|
this.setEdit();
|
||||||
},
|
},
|
||||||
|
@ -84,7 +84,7 @@ class Message extends SnowFlake{
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
Message.contextmenu.addbutton(
|
Message.contextmenu.addbutton(
|
||||||
"Delete message",
|
()=>I18n.getTranslation("message.delete"),
|
||||||
function(this: Message){
|
function(this: Message){
|
||||||
this.delete();
|
this.delete();
|
||||||
},
|
},
|
||||||
|
|
|
@ -21,6 +21,37 @@ body {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
#searchOptions{
|
||||||
|
padding:.05in .15in;
|
||||||
|
border-radius: .1in;
|
||||||
|
background: var(--channels-bg);
|
||||||
|
position:absolute;
|
||||||
|
bottom:0;
|
||||||
|
width: calc(100% - 32px);
|
||||||
|
box-sizing: border-box;
|
||||||
|
span {
|
||||||
|
transition: background .1s;
|
||||||
|
margin-bottom:.025in;
|
||||||
|
margin-top:.025in;
|
||||||
|
padding:.075in .05in;
|
||||||
|
border-radius:.03in;
|
||||||
|
cursor:pointer;
|
||||||
|
}
|
||||||
|
span.selected{
|
||||||
|
background:var(--button-bg);
|
||||||
|
}
|
||||||
|
span:hover{
|
||||||
|
background:var(--button-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
;
|
||||||
|
margin: 16px;
|
||||||
|
border: solid .025in var(--black);
|
||||||
|
}
|
||||||
|
#searchOptions:empty{
|
||||||
|
padding: 0;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
.flexgrow {
|
.flexgrow {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
|
|
|
@ -309,7 +309,8 @@
|
||||||
},
|
},
|
||||||
"message":{
|
"message":{
|
||||||
"reactionAdd":"Add reaction",
|
"reactionAdd":"Add reaction",
|
||||||
"delete":"Delete message"
|
"delete":"Delete message",
|
||||||
|
"edit":"Edit message"
|
||||||
},
|
},
|
||||||
"instanceStats":{
|
"instanceStats":{
|
||||||
"name":"Instance stats: $1",
|
"name":"Instance stats: $1",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue