Compare commits

..

No commits in common. "main" and "main" have entirely different histories.
main ... main

View File

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