Merge branch 'main' into feat/push-notifications
This commit is contained in:
		
						commit
						e380a1b9e7
					
				| @ -2,29 +2,27 @@ import { app } from "../app.js"; | ||||
| 
 | ||||
| class ChatInputComponent extends HTMLElement { | ||||
|   autoCompletions = { | ||||
|     "example 1": () => {}, | ||||
|     "example 2": () => {}, | ||||
|     "example 1": () => { | ||||
|     }, | ||||
|     "example 2": () => { | ||||
|     }, | ||||
|   } | ||||
|   hiddenCompletions = { | ||||
|     "/starsRender": () => { | ||||
|         app.rpc.starsRender(this.channelUid,this.value.replace("/starsRender ","")) | ||||
|       app.rpc.starsRender(this.channelUid, this.value.replace("/starsRender ", "")) | ||||
|     } | ||||
|   } | ||||
|   users = [] | ||||
|   textarea = null  | ||||
|   _value = ""  | ||||
|   lastUpdateEvent = null  | ||||
|   previousValue = ""  | ||||
|   lastChange = null  | ||||
|   changed = false  | ||||
|   textarea = null | ||||
|   _value = "" | ||||
|   lastUpdateEvent = null | ||||
|   expiryTimer = null; | ||||
| 
 | ||||
|   constructor() { | ||||
|     super(); | ||||
|     this.lastUpdateEvent = new Date(); | ||||
|     this.textarea = document.createElement("textarea"); | ||||
|     this.value = this.getAttribute("value") || ""; | ||||
|     this.previousValue = this.value; | ||||
|     this.lastChange = new Date(); | ||||
|     this.changed = false; | ||||
|   } | ||||
| 
 | ||||
|   get value() { | ||||
| @ -32,24 +30,27 @@ class ChatInputComponent extends HTMLElement { | ||||
|   } | ||||
| 
 | ||||
|   set value(value) { | ||||
|     this._value = value || ""; | ||||
|     this._value = value; | ||||
|     this.textarea.value = this._value; | ||||
|   } | ||||
| 
 | ||||
|   get allAutoCompletions() { | ||||
|      return Object.assign({},this.autoCompletions,this.hiddenCompletions)  | ||||
|     return Object.assign({}, this.autoCompletions, this.hiddenCompletions) | ||||
|   } | ||||
| 
 | ||||
|   resolveAutoComplete() { | ||||
|     let count = 0; | ||||
|     let value = null; | ||||
|       | ||||
|     Object.keys(this.allAutoCompletions).forEach((key) => { | ||||
|         if (key.startsWith(this.value.split(" ")[0])) { | ||||
|         count++; | ||||
| 
 | ||||
|     for (const key of Object.keys(this.allAutoCompletions)) { | ||||
|       if (key.startsWith(this.value.split(" ", 1)[0])) { | ||||
|         if (value) { | ||||
|           return null; | ||||
|         } | ||||
|         value = key; | ||||
|       } | ||||
|     }); | ||||
|     if (count == 1) return value; | ||||
|     return null; | ||||
|     } | ||||
| 
 | ||||
|     return value; | ||||
|   } | ||||
| 
 | ||||
|   isActive() { | ||||
| @ -58,117 +59,103 @@ class ChatInputComponent extends HTMLElement { | ||||
| 
 | ||||
|   focus() { | ||||
|     this.textarea.focus(); | ||||
|   }  | ||||
|     getAuthors(){ | ||||
|         let authors = [] | ||||
|         for (let i = 0; i < this.users.length; i++) { | ||||
|             authors.push(this.users[i].username) | ||||
|             authors.push(this.users[i].nick) | ||||
|         } | ||||
|         return authors | ||||
|          | ||||
|     } | ||||
|  extractMentions(text) { | ||||
|   const regex = /@([a-zA-Z0-9_-]+)/g; | ||||
|   const mentions = []; | ||||
|   let match; | ||||
| 
 | ||||
|   while ((match = regex.exec(text)) !== null) { | ||||
|     mentions.push(match[1]); | ||||
|   } | ||||
| 
 | ||||
|   return mentions; | ||||
| } | ||||
|     matchMentionsToAuthors(mentions, authors) { | ||||
|   return mentions.map(mention => { | ||||
|     let closestAuthor = null; | ||||
|     let minDistance = Infinity; | ||||
|      const lowerMention = mention.toLowerCase(); | ||||
|      | ||||
|     authors.forEach(author => { | ||||
|     const lowerAuthor = author.toLowerCase(); | ||||
|       let distance = this.levenshteinDistance(lowerMention, lowerAuthor); | ||||
|       | ||||
|   getAuthors() { | ||||
|     return this.users.flatMap((user) => [user.username, user.nick]) | ||||
|   } | ||||
| 
 | ||||
|         if(!this.isSubsequence(lowerMention,lowerAuthor)) { | ||||
|             distance += 10         | ||||
|   extractMentions(text) { | ||||
|     return Array.from(text.matchAll(/@([a-zA-Z0-9_-]+)/g), m => m[1]); | ||||
|   } | ||||
| 
 | ||||
|   matchMentionsToAuthors(mentions, authors) { | ||||
|     return mentions.map(mention => { | ||||
|       let closestAuthor = null; | ||||
|       let minDistance = Infinity; | ||||
|       const lowerMention = mention.toLowerCase(); | ||||
| 
 | ||||
|       authors.forEach(author => { | ||||
|         const lowerAuthor = author.toLowerCase(); | ||||
|         let distance = this.levenshteinDistance(lowerMention, lowerAuthor); | ||||
| 
 | ||||
| 
 | ||||
|         if (!this.isSubsequence(lowerMention, lowerAuthor)) { | ||||
|           distance += 10 | ||||
|         } | ||||
| 
 | ||||
|       if (distance < minDistance) { | ||||
|         minDistance = distance; | ||||
|         closestAuthor = author; | ||||
|         if (distance < minDistance) { | ||||
|           minDistance = distance; | ||||
|           closestAuthor = author; | ||||
| 
 | ||||
|         } | ||||
| 
 | ||||
|       }); | ||||
| 
 | ||||
|       return { mention, closestAuthor, distance: minDistance }; | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   levenshteinDistance(a, b) { | ||||
|     const matrix = []; | ||||
| 
 | ||||
|     // Initialize the first row and column
 | ||||
|     for (let i = 0; i <= b.length; i++) { | ||||
|       matrix[i] = [i]; | ||||
|     } | ||||
|     for (let j = 0; j <= a.length; j++) { | ||||
|       matrix[0][j] = j; | ||||
|     } | ||||
| 
 | ||||
|     // Fill in the matrix
 | ||||
|     for (let i = 1; i <= b.length; i++) { | ||||
|       for (let j = 1; j <= a.length; j++) { | ||||
|         if (b.charAt(i - 1) === a.charAt(j - 1)) { | ||||
|           matrix[i][j] = matrix[i - 1][j - 1]; | ||||
|         } else { | ||||
|           matrix[i][j] = Math.min( | ||||
|             matrix[i - 1][j] + 1,    // Deletion
 | ||||
|             matrix[i][j - 1] + 1,    // Insertion
 | ||||
|             matrix[i - 1][j - 1] + 1 // Substitution
 | ||||
|           ); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     return matrix[b.length][a.length]; | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   replaceMentionsWithAuthors(text) { | ||||
|     const authors = this.getAuthors(); | ||||
|     const mentions = this.extractMentions(text); | ||||
| 
 | ||||
|     const matches = this.matchMentionsToAuthors(mentions, authors); | ||||
|     let updatedText = text; | ||||
|     matches.forEach(({ mention, closestAuthor }) => { | ||||
|       const mentionRegex = new RegExp(`@${mention}`, 'g'); | ||||
|       updatedText = updatedText.replace(mentionRegex, `@${closestAuthor}`); | ||||
|     }); | ||||
| 
 | ||||
|     return { mention, closestAuthor, distance: minDistance }; | ||||
|   }); | ||||
| } | ||||
| levenshteinDistance(a, b) { | ||||
|   const matrix = []; | ||||
| 
 | ||||
|   // Initialize the first row and column
 | ||||
|   for (let i = 0; i <= b.length; i++) { | ||||
|     matrix[i] = [i]; | ||||
|     return updatedText; | ||||
|   } | ||||
|   for (let j = 0; j <= a.length; j++) { | ||||
|     matrix[0][j] = j; | ||||
|   } | ||||
| 
 | ||||
|   // Fill in the matrix
 | ||||
|   for (let i = 1; i <= b.length; i++) { | ||||
|     for (let j = 1; j <= a.length; j++) { | ||||
|       if (b.charAt(i - 1) === a.charAt(j - 1)) { | ||||
|         matrix[i][j] = matrix[i - 1][j - 1]; | ||||
|       } else { | ||||
|         matrix[i][j] = Math.min( | ||||
|           matrix[i - 1][j] + 1,    // Deletion
 | ||||
|           matrix[i][j - 1] + 1,    // Insertion
 | ||||
|           matrix[i - 1][j - 1] + 1 // Substitution
 | ||||
|         ); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   return matrix[b.length][a.length]; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|       replaceMentionsWithAuthors(text) { | ||||
|     const authors = this.getAuthors(); | ||||
|   const mentions = this.extractMentions(text); | ||||
|    | ||||
|   const matches = this.matchMentionsToAuthors(mentions, authors); | ||||
|   let updatedText = text; | ||||
|   matches.forEach(({ mention, closestAuthor }) => { | ||||
|     const mentionRegex = new RegExp(`@${mention}`, 'g'); | ||||
|       updatedText = updatedText.replace(mentionRegex, `@${closestAuthor}`); | ||||
|   }); | ||||
|    | ||||
|   return updatedText; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|   async connectedCallback() { | ||||
|     this.user = null  | ||||
|     this.user = null | ||||
|     app.rpc.getUser(null).then((user) => { | ||||
|         this.user=user | ||||
|       this.user = user | ||||
|     }) | ||||
| 
 | ||||
| 
 | ||||
|       const me = this; | ||||
|     this.liveType = this.getAttribute("live-type") === "true"; | ||||
|     this.liveTypeInterval = | ||||
|       parseInt(this.getAttribute("live-type-interval")) || 6; | ||||
|     this.channelUid = this.getAttribute("channel"); | ||||
|      | ||||
|     app.rpc.getRecentUsers(this.channelUid).then(users=>{ | ||||
|         this.users = users     | ||||
| 
 | ||||
|     app.rpc.getRecentUsers(this.channelUid).then(users => { | ||||
|       this.users = users | ||||
|     }) | ||||
|       this.messageUid = null; | ||||
|     this.messageUid = null; | ||||
| 
 | ||||
|     this.classList.add("chat-input"); | ||||
| 
 | ||||
| @ -190,18 +177,33 @@ levenshteinDistance(a, b) { | ||||
| 
 | ||||
|     this.textarea.addEventListener("keyup", (e) => { | ||||
|       if (e.key === "Enter" && !e.shiftKey) { | ||||
|         this.value = ""; | ||||
| 
 | ||||
|         const message = this.replaceMentionsWithAuthors(this.value); | ||||
|         e.target.value = ""; | ||||
| 
 | ||||
|         if (!message) { | ||||
|           return; | ||||
|         } | ||||
|         let autoCompletionHandler = this.allAutoCompletions[this.value.split(" ", 1)[0]]; | ||||
|         if (autoCompletionHandler) { | ||||
|           autoCompletionHandler(); | ||||
|           this.value = ""; | ||||
|           e.target.value = ""; | ||||
| 
 | ||||
|           return; | ||||
|         } | ||||
| 
 | ||||
|         this.finalizeMessage() | ||||
| 
 | ||||
|         return; | ||||
|       } | ||||
|       this.value = e.target.value; | ||||
|       this.changed = true; | ||||
|       this.update(); | ||||
| 
 | ||||
|       this.updateFromInput(e.target.value); | ||||
|     }); | ||||
| 
 | ||||
|     this.textarea.addEventListener("keydown", (e) => { | ||||
|       this.value = e.target.value; | ||||
|       let autoCompletion = null;  | ||||
|       let autoCompletion = null; | ||||
|       if (e.key === "Tab") { | ||||
|         e.preventDefault(); | ||||
|         autoCompletion = this.resolveAutoComplete(); | ||||
| @ -213,160 +215,94 @@ levenshteinDistance(a, b) { | ||||
|       } | ||||
|       if (e.key === "Enter" && !e.shiftKey) { | ||||
|         e.preventDefault(); | ||||
| 
 | ||||
|         const message = me.replaceMentionsWithAuthors(this.value); | ||||
|         e.target.value = ""; | ||||
| 
 | ||||
|         if (!message) { | ||||
|           return; | ||||
|         } | ||||
|           let autoCompletionHandler = this.allAutoCompletions[this.value.split(" ")[0]]; | ||||
|         if (autoCompletionHandler) { | ||||
|           autoCompletionHandler(); | ||||
|             this.value = ""; | ||||
|           this.previousValue = ""; | ||||
|           e.target.value = ""; | ||||
| 
 | ||||
|           return; | ||||
|         } | ||||
| 
 | ||||
|         this.updateMessage() | ||||
|         app.rpc.finalizeMessage(this.messageUid) | ||||
|         this.value = ""; | ||||
|         this.previousValue = ""; | ||||
|         this.messageUid = null; | ||||
|       } | ||||
|     }); | ||||
| 
 | ||||
|     this.changeInterval = setInterval(() => { | ||||
|       if (!this.liveType) { | ||||
|         return; | ||||
|       } | ||||
|       if (this.value !== this.previousValue) { | ||||
|         if ( | ||||
|           this.trackSecondsBetweenEvents(this.lastChange, new Date()) >= | ||||
|           this.liveTypeInterval | ||||
|         ) { | ||||
|           this.value = ""; | ||||
|           this.previousValue = ""; | ||||
|         } | ||||
|         this.lastChange = new Date(); | ||||
|       } | ||||
|       this.update(); | ||||
|     }, 300); | ||||
| 
 | ||||
|     this.addEventListener("upload", (e) => { | ||||
|       this.focus(); | ||||
|     }); | ||||
|     this.addEventListener("uploaded", function (e) { | ||||
|       let message = ""; | ||||
|       e.detail.files.forEach((file) => { | ||||
|         message += `[${file.name}](/channel/attachment/${file.relative_url})`; | ||||
|       }); | ||||
|       app.rpc.sendMessage(this.channelUid, message,true); | ||||
|       let message = e.detail.files.reduce((message, file) => { | ||||
|         return `${message}[${file.name}](/channel/attachment/${file.relative_url})`; | ||||
|       }, ''); | ||||
|       app.rpc.sendMessage(this.channelUid, message, true); | ||||
|     }); | ||||
|     setTimeout(()=>{ | ||||
|     setTimeout(() => { | ||||
|       this.focus(); | ||||
|     },1000) | ||||
|     }, 1000) | ||||
|   } | ||||
| 
 | ||||
|   trackSecondsBetweenEvents(event1Time, event2Time) { | ||||
|     const millisecondsDifference = event2Time.getTime() - event1Time.getTime(); | ||||
|     return millisecondsDifference / 1000; | ||||
|   } | ||||
| 
 | ||||
|   isSubsequence(s, t) { | ||||
|       let i = 0, j = 0; | ||||
|       while (i < s.length && j < t.length) { | ||||
|         if (s[i] === t[j]) { | ||||
|           i++; | ||||
|         } | ||||
|         j++; | ||||
|     let i = 0, j = 0; | ||||
|     while (i < s.length && j < t.length) { | ||||
|       if (s[i] === t[j]) { | ||||
|         i++; | ||||
|       } | ||||
|       return i === s.length;  | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|   newMessage() { | ||||
|     if (!this.messageUid) { | ||||
|       this.messageUid = "?"; | ||||
|     } | ||||
| 
 | ||||
|     this.value = this.replaceMentionsWithAuthors(this.value); | ||||
|     this.sendMessage(this.channelUid, this.value,!this.liveType).then((uid) => { | ||||
|         if (this.liveType) { | ||||
|             this.messageUid = uid; | ||||
|         } | ||||
|     }); | ||||
|     } | ||||
| 
 | ||||
|   updateMessage() { | ||||
|     if (this.value[0] == "/") { | ||||
|       return false; | ||||
|     } | ||||
|     if (!this.messageUid) { | ||||
|       this.newMessage(); | ||||
|       return false; | ||||
|     } | ||||
|     if (this.messageUid === "?") { | ||||
|       return false; | ||||
|     } | ||||
|     if ( | ||||
|       typeof app !== "undefined" && | ||||
|       app.rpc && | ||||
|       typeof app.rpc.updateMessageText === "function" | ||||
|     ) { | ||||
|       app.rpc.updateMessageText(this.messageUid, this.replaceMentionsWithAuthors(this.value)); | ||||
|       j++; | ||||
|     } | ||||
|     return i === s.length; | ||||
|   } | ||||
| 
 | ||||
|   updateStatus() { | ||||
|     if (this.liveType) { | ||||
|       return; | ||||
|     } | ||||
|     if (this.trackSecondsBetweenEvents(this.lastUpdateEvent, new Date()) > 1) { | ||||
|   flagTyping() { | ||||
|     if (this.trackSecondsBetweenEvents(this.lastUpdateEvent, new Date()) >= 1) { | ||||
|       this.lastUpdateEvent = new Date(); | ||||
|       if ( | ||||
|         typeof app !== "undefined" && | ||||
|         app.rpc && | ||||
|         typeof app.rpc.set_typing === "function" | ||||
|       ) { | ||||
|         app.rpc.set_typing(this.channelUid, this.user.color); | ||||
|       app.rpc.set_typing(this.channelUid, this.user.color).catch(() => { | ||||
|       }); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   finalizeMessage() { | ||||
|     if (!this.messageUid) { | ||||
|       if (this.value.trim() === "") { | ||||
|         return; | ||||
|       } | ||||
|       this.sendMessage(this.channelUid, this.replaceMentionsWithAuthors(this.value), !this.liveType); | ||||
|     } else { | ||||
|       app.rpc.finalizeMessage(this.messageUid) | ||||
|     } | ||||
|     this.value = ""; | ||||
|     this.messageUid = null; | ||||
|   } | ||||
| 
 | ||||
|   updateFromInput(value) { | ||||
|     if (this.expiryTimer) { | ||||
|       clearTimeout(this.expiryTimer); | ||||
|       this.expiryTimer = null; | ||||
|     } | ||||
| 
 | ||||
|     this.value = value; | ||||
| 
 | ||||
|     this.flagTyping() | ||||
| 
 | ||||
|     if (this.liveType && value[0] !== "/") { | ||||
|       this.expiryTimer = setTimeout(() => { | ||||
|         this.finalizeMessage() | ||||
|       }, this.liveTypeInterval * 1000); | ||||
| 
 | ||||
|       if (this.messageUid === "?") { | ||||
|       } else if (this.messageUid) { | ||||
|         app.rpc.updateMessageText(this.messageUid, this.replaceMentionsWithAuthors(this.value)); | ||||
|       } else { | ||||
|         this.messageUid = "?"; // Indicate that a message is being sent
 | ||||
|         this.sendMessage(this.channelUid, this.replaceMentionsWithAuthors(value), !this.liveType).then((uid) => { | ||||
|           if (this.liveType) { | ||||
|             this.messageUid = uid; | ||||
|           } | ||||
|         }); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   update() { | ||||
|     const expired = | ||||
|       this.trackSecondsBetweenEvents(this.lastChange, new Date()) >= | ||||
|       this.liveTypeInterval; | ||||
|     const changed = this.value !== this.previousValue; | ||||
| 
 | ||||
|     if (changed || expired) { | ||||
|       this.lastChange = new Date(); | ||||
|       this.updateStatus(); | ||||
|     } | ||||
| 
 | ||||
|     this.previousValue = this.value; | ||||
| 
 | ||||
|     if (this.liveType && expired) { | ||||
|       this.value = ""; | ||||
|       this.previousValue = ""; | ||||
|       this.messageUid = null; | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     if (changed) { | ||||
|       if (this.liveType) { | ||||
|         this.updateMessage(); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   async sendMessage(channelUid, value,is_final) { | ||||
|   async sendMessage(channelUid, value, is_final) { | ||||
|     if (!value.trim()) { | ||||
|       return null; | ||||
|     } | ||||
|     return await app.rpc.sendMessage(channelUid, value,is_final); | ||||
|     return await app.rpc.sendMessage(channelUid, value, is_final); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user