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);
|
||||
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{
|
||||
const typingtext = document.getElementById("typing") as HTMLDivElement;
|
||||
let build = "";
|
||||
|
|
|
@ -74,6 +74,9 @@
|
|||
<div id="loadingdiv">
|
||||
</div>
|
||||
</div>
|
||||
<div style="position: relative;">
|
||||
<div id="searchOptions" class="flexttb"></div>
|
||||
</div>
|
||||
<div id="pasteimage" class="flexltr"></div>
|
||||
<div id="replybox" class="hideReplyBox"></div>
|
||||
<div id="typediv">
|
||||
|
|
|
@ -157,6 +157,7 @@ import { I18n } from "./i18n.js";
|
|||
let replyingTo: Message | null = null;
|
||||
|
||||
async function handleEnter(event: KeyboardEvent): Promise<void>{
|
||||
if(thisUser.keyup(event)){return}
|
||||
const channel = thisUser.channelfocus;
|
||||
if(!channel)return;
|
||||
|
||||
|
@ -198,15 +199,14 @@ import { I18n } from "./i18n.js";
|
|||
}
|
||||
}
|
||||
|
||||
interface CustomHTMLDivElement extends HTMLDivElement {
|
||||
markdown: MarkDown;
|
||||
}
|
||||
interface CustomHTMLDivElement extends HTMLDivElement {markdown: MarkDown;}
|
||||
|
||||
const typebox = document.getElementById("typebox") as CustomHTMLDivElement;
|
||||
const markdown = new MarkDown("", thisUser);
|
||||
typebox.markdown = markdown;
|
||||
typebox.addEventListener("keyup", handleEnter);
|
||||
typebox.addEventListener("keydown", event=>{
|
||||
thisUser.keydown(event)
|
||||
if(event.key === "Enter" && !event.shiftKey) event.preventDefault();
|
||||
});
|
||||
markdown.giveBox(typebox);
|
||||
|
|
|
@ -5,10 +5,10 @@ import{ AVoice }from"./audio.js";
|
|||
import{ User }from"./user.js";
|
||||
import{ Dialog }from"./dialog.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{ 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 { Role } from "./role.js";
|
||||
import { VoiceFactory } from "./voice.js";
|
||||
|
@ -67,6 +67,7 @@ class Localuser{
|
|||
};
|
||||
}
|
||||
async gottenReady(ready: readyjson): Promise<void>{
|
||||
|
||||
await I18n.done;
|
||||
this.initialized = true;
|
||||
this.ready = ready;
|
||||
|
@ -77,6 +78,8 @@ class Localuser{
|
|||
this.resume_gateway_url=ready.d.resume_gateway_url;
|
||||
this.session_id=ready.d.session_id;
|
||||
|
||||
this.mdBox();
|
||||
|
||||
this.voiceFactory=new VoiceFactory({id:this.user.id});
|
||||
this.handleVoice();
|
||||
this.mfa_enabled = ready.d.user.mfa_enabled as boolean;
|
||||
|
@ -1711,11 +1714,226 @@ 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.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 -----------
|
||||
readonly waitingmembers: Map<
|
||||
string,
|
||||
Map<string, (returns: memberjson | undefined) => void>
|
||||
> = new Map();
|
||||
readonly waitingmembers = new Map<string,Map<string, (returns: memberjson | undefined) => void>>();
|
||||
readonly presences: Map<string, presencejson> = new Map();
|
||||
async resolvemember(
|
||||
id: string,
|
||||
|
@ -1757,6 +1975,14 @@ class Localuser{
|
|||
fetchingmembers: Map<string, boolean> = new Map();
|
||||
noncemap: Map<string, (r: [memberjson[], string[]]) => void> = 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: {
|
||||
chunk_index: number;
|
||||
chunk_count: number;
|
||||
|
@ -1770,6 +1996,14 @@ class Localuser{
|
|||
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 ??= [];
|
||||
const arr = this.noncebuild.get(chunk.nonce);
|
||||
if(!arr)return;
|
||||
|
|
|
@ -687,7 +687,9 @@ txt[j + 1] === undefined)
|
|||
e.target.classList.remove("spoiler");
|
||||
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 = _=>{
|
||||
//console.log(_);
|
||||
};
|
||||
|
@ -698,7 +700,9 @@ txt[j + 1] === undefined)
|
|||
prevcontent = content;
|
||||
this.txt = content.split("");
|
||||
this.boxupdate(box);
|
||||
MarkDown.gatherBoxText(box);
|
||||
}
|
||||
|
||||
};
|
||||
box.onpaste = _=>{
|
||||
if(!_.clipboardData)return;
|
||||
|
@ -717,7 +721,10 @@ txt[j + 1] === undefined)
|
|||
box.append(this.makeHTML({ keep: true }));
|
||||
if(restore){
|
||||
restore();
|
||||
const test=saveCaretPosition(box);
|
||||
if(test) test();
|
||||
}
|
||||
this.onUpdate(text,formatted);
|
||||
}
|
||||
static gatherBoxText(element: HTMLElement): string{
|
||||
if(element.tagName.toLowerCase() === "img"){
|
||||
|
@ -729,11 +736,17 @@ txt[j + 1] === undefined)
|
|||
if(element.hasAttribute("real")){
|
||||
return element.getAttribute("real") as string;
|
||||
}
|
||||
if(element.tagName.toLowerCase() === "pre"||element.tagName.toLowerCase() === "samp"){
|
||||
formatted=true;
|
||||
}else{
|
||||
formatted=false;
|
||||
}
|
||||
let build = "";
|
||||
for(const thing of Array.from(element.childNodes)){
|
||||
if(thing instanceof Text){
|
||||
const text = thing.textContent;
|
||||
build += text;
|
||||
|
||||
continue;
|
||||
}
|
||||
const text = this.gatherBoxText(thing as HTMLElement);
|
||||
|
@ -747,10 +760,7 @@ txt[j + 1] === undefined)
|
|||
static safeLink(elm: HTMLElement, url: string){
|
||||
if(URL.canParse(url)){
|
||||
const Url = new URL(url);
|
||||
if(
|
||||
elm instanceof HTMLAnchorElement &&
|
||||
this.trustedDomains.has(Url.host)
|
||||
){
|
||||
if(elm instanceof HTMLAnchorElement && this.trustedDomains.has(Url.host)){
|
||||
elm.href = url;
|
||||
elm.target = "_blank";
|
||||
return;
|
||||
|
@ -820,20 +830,81 @@ txt[j + 1] === undefined)
|
|||
|
||||
//solution from https://stackoverflow.com/questions/4576694/saving-and-restoring-caret-position-for-contenteditable-div
|
||||
let text = "";
|
||||
function saveCaretPosition(context: Node){
|
||||
const selection = window.getSelection();
|
||||
let formatted=false;
|
||||
function saveCaretPosition(context: HTMLElement){
|
||||
const selection = window.getSelection() as Selection;
|
||||
if(!selection)return;
|
||||
const range = selection.getRangeAt(0);
|
||||
range.setStart(context, 0);
|
||||
text = selection.toString();
|
||||
let len = text.length + 1;
|
||||
for(const str in text.split("\n")){
|
||||
if(str.length !== 0){
|
||||
len--;
|
||||
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;
|
||||
}
|
||||
}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(){
|
||||
if(!selection)return;
|
||||
const pos = getTextNodeAtPosition(context, len);
|
||||
|
@ -844,20 +915,49 @@ function saveCaretPosition(context: Node){
|
|||
};
|
||||
}
|
||||
|
||||
function getTextNodeAtPosition(root: Node, index: number){
|
||||
const NODE_TYPE = NodeFilter.SHOW_TEXT;
|
||||
const treeWalker = document.createTreeWalker(root, NODE_TYPE, elem=>{
|
||||
if(!elem.textContent)return 0;
|
||||
if(index > elem.textContent.length){
|
||||
index -= elem.textContent.length;
|
||||
return NodeFilter.FILTER_REJECT;
|
||||
}
|
||||
return NodeFilter.FILTER_ACCEPT;
|
||||
});
|
||||
const c = treeWalker.nextNode();
|
||||
function getTextNodeAtPosition(root: Node, index: number):{
|
||||
node: Node,
|
||||
position: number,
|
||||
}{
|
||||
if(root instanceof Text){
|
||||
return{
|
||||
node: c ? c : root,
|
||||
node: root,
|
||||
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;
|
||||
}
|
||||
}
|
||||
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(
|
||||
user: User,
|
||||
guild: Guild
|
||||
|
|
|
@ -74,7 +74,7 @@ class Message extends SnowFlake{
|
|||
}
|
||||
);
|
||||
Message.contextmenu.addbutton(
|
||||
()=>I18n.getTranslation("message.delete"),
|
||||
()=>I18n.getTranslation("message.edit"),
|
||||
function(this: Message){
|
||||
this.setEdit();
|
||||
},
|
||||
|
@ -84,7 +84,7 @@ class Message extends SnowFlake{
|
|||
}
|
||||
);
|
||||
Message.contextmenu.addbutton(
|
||||
"Delete message",
|
||||
()=>I18n.getTranslation("message.delete"),
|
||||
function(this: Message){
|
||||
this.delete();
|
||||
},
|
||||
|
|
|
@ -21,6 +21,37 @@ body {
|
|||
display: flex;
|
||||
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 {
|
||||
flex-grow: 1;
|
||||
min-height: 0;
|
||||
|
|
|
@ -309,7 +309,8 @@
|
|||
},
|
||||
"message":{
|
||||
"reactionAdd":"Add reaction",
|
||||
"delete":"Delete message"
|
||||
"delete":"Delete message",
|
||||
"edit":"Edit message"
|
||||
},
|
||||
"instanceStats":{
|
||||
"name":"Instance stats: $1",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue