diff --git a/src/webpage/bot.ts b/src/webpage/bot.ts index 798825af..afaed549 100644 --- a/src/webpage/bot.ts +++ b/src/webpage/bot.ts @@ -122,6 +122,7 @@ class Bot { }); const bioBox = settingsLeft.addMDInput(I18n.bio(), (_) => {}, { initText: bot.bio.rawString, + maxLength: this.localuser.instanceLimits.user?.maxBio ?? 9999 }); bioBox.watchForChange((_) => { newbio = _; diff --git a/src/webpage/channel.ts b/src/webpage/channel.ts index 427fb481..7cdb8384 100644 --- a/src/webpage/channel.ts +++ b/src/webpage/channel.ts @@ -413,9 +413,11 @@ class Channel extends SnowFlake { }); form.addTextInput(I18n.channel["name:"](), "name", { initText: this.name, + maxLength: this.localuser.instanceLimits.channel?.maxName ?? 9999, }); form.addMDInput(I18n.channel["topic:"](), "topic", { initText: this.topic, + maxLength: this.localuser.instanceLimits.channel?.maxTopic ?? 9999, }); form.addImageInput(I18n.channel.icon(), "icon", { initImg: this.icon ? this.iconUrl() : undefined, diff --git a/src/webpage/direct.ts b/src/webpage/direct.ts index b264f039..7f7075b6 100644 --- a/src/webpage/direct.ts +++ b/src/webpage/direct.ts @@ -505,6 +505,7 @@ class Group extends Channel { }); form.addTextInput(I18n.channel["name:"](), "name", { initText: this.name === this.defaultName() ? "" : this.name, + maxLength: this.localuser.instanceLimits.channel?.maxName ?? 9999, }); form.addImageInput(I18n.channel.icon(), "icon", { initImg: this.icon ? this.iconUrl() : undefined, diff --git a/src/webpage/localuser.ts b/src/webpage/localuser.ts index 156a0689..92551193 100644 --- a/src/webpage/localuser.ts +++ b/src/webpage/localuser.ts @@ -98,6 +98,7 @@ class Localuser { readonly userMap: Map = new Map(); voiceFactory?: VoiceFactory; play?: Play; + instanceLimits: any; instancePing = { name: "Unknown", }; @@ -256,6 +257,18 @@ class Localuser { if (this.perminfo.user.disableColors === undefined) this.perminfo.user.disableColors = true; this.updateTranslations(); } + + async loadInstanceLimits(instanceUrl: string) { + const res = await fetch(`${instanceUrl}/policies/instance/limits/`, { + method: "GET", + }) + .then((response) => response.json()) + .then((json) => { + return json; + }); + this.instanceLimits = res; + } + favorites!: Favorites; readysup = false; get voiceAllowed() { @@ -1562,6 +1575,7 @@ class Localuser { await guild.loadChannel(location[5], true, location[6]); this.channelfocus = this.channelids.get(location[5]); } + await this.loadInstanceLimits(this.info.api); } loaduser(): void { (document.getElementById("username") as HTMLSpanElement).textContent = this.user.username; @@ -2404,11 +2418,12 @@ class Localuser { }); const bioBox = settingsLeft.addMDInput(I18n.bio(), (_) => {}, { initText: this.user.bio.rawString, + maxLength: this.instanceLimits.user?.maxBio ?? 9999 }); bioBox.watchForChange((_) => { - newbio = _; - hypouser.bio = new MarkDown(_, this); - regen(); + newbio = _; + hypouser.bio = new MarkDown(_, this); + regen(); }); if (this.user.accent_color) { @@ -2797,7 +2812,9 @@ class Localuser { if (this.mfa_enabled) { form.addTextInput(I18n.localuser["2faCode:"](), "code"); } - form.addTextInput(I18n.localuser.newUsername(), "username"); + form.addTextInput(I18n.localuser.newUsername(), "username", { + maxLength: this.instanceLimits.user?.maxUsername ?? 9999 + }); }); security.addButtonInput("", I18n.localuser.changePassword(), () => { const form = security.addSubForm( @@ -3674,6 +3691,7 @@ class Localuser { ); form.addTextInput(I18n.localuser.botUsername(), "username", { initText: bot.username, + maxLength: this.instanceLimits.user?.maxUsername ?? 9999, }); form.addImageInput(I18n.localuser.botAvatar(), "avatar", { initImg: bot.getpfpsrc(), diff --git a/src/webpage/member.ts b/src/webpage/member.ts index 496fa6b8..8ac2708a 100644 --- a/src/webpage/member.ts +++ b/src/webpage/member.ts @@ -188,6 +188,7 @@ class Member extends SnowFlake { ); form.addTextInput(I18n.member["nick:"](), "nick", { initText: this.nick, + maxLength: this.localuser.instanceLimits.user?.maxUsername ?? 9999, }); dio.show(); } @@ -214,6 +215,7 @@ class Member extends SnowFlake { const nicky = settingsLeft.addTextInput(I18n.member["nick:"](), () => {}, { initText: this.nick || "", + maxLength: this.localuser.instanceLimits.user?.maxUsername ?? 9999, }); nicky.watchForChange((_) => { hypomember.nick = _; @@ -294,6 +296,7 @@ class Member extends SnowFlake { }); const bioBox = settingsLeft.addMDInput(I18n.bio(), (_) => {}, { initText: this.bio, + maxLength: this.localuser.instanceLimits.user?.maxBio ?? 9999 }); bioBox.watchForChange((_) => { newbio = _; diff --git a/src/webpage/settings.ts b/src/webpage/settings.ts index 8575f479..d3b0ba6b 100644 --- a/src/webpage/settings.ts +++ b/src/webpage/settings.ts @@ -137,14 +137,17 @@ class TextInput implements OptionsElement { value: string; input!: WeakRef; password: boolean; + html_div!: HTMLDivElement; + maxLength: number; constructor( label: string, onSubmit: (str: string) => void, owner: Options, - {initText = "", password = false} = {}, + {initText = "", password = false, maxLength=9999} = {}, ) { this.label = label; this.value = initText; + this.maxLength = maxLength; this.owner = owner; this.onSubmit = onSubmit; this.password = password; @@ -156,10 +159,12 @@ class TextInput implements OptionsElement { div.append(span); const input = document.createElement("input"); input.value = this.value; + input.maxLength = this.maxLength; input.type = this.password ? "password" : "text"; input.oninput = this.onChange.bind(this); this.input = new WeakRef(input); div.append(input); + this.html_div = div; return div; } onChange() { @@ -169,6 +174,10 @@ class TextInput implements OptionsElement { const value = input.value as string; this.onchange(value); this.value = value; + if (value.length == this.maxLength) {; + let limit = " (" + this.maxLength.toString() + ")"; + this.makeError(I18n.localuser.inputLengthLimit() + limit); + } } } onchange: (str: string) => void = (_) => {}; @@ -178,6 +187,21 @@ class TextInput implements OptionsElement { submit() { this.onSubmit(this.value); } + makeError(message: string) { + let element = this.html_div.getElementsByClassName("suberror")[0] as HTMLElement; + if (!element) { + const div = document.createElement("div"); + div.classList.add("suberror", "suberrora"); + this.html_div.append(div); + element = div; + } else { + element.classList.remove("suberror"); + setTimeout((_) => { + element.classList.add("suberror"); + }, 100); + } + element.textContent = message; + } } class DateInput extends TextInput { generateHTML(): HTMLDivElement { @@ -528,14 +552,17 @@ class MDInput implements OptionsElement { readonly onSubmit: (str: string) => void; value: string; input!: WeakRef; + html_div!: HTMLDivElement; + maxLength: number; constructor( label: string, onSubmit: (str: string) => void, owner: Options, - {initText = ""} = {}, + {initText = "", maxLength=9999} = {}, ) { this.label = label; this.value = initText; + this.maxLength = maxLength; this.owner = owner; this.onSubmit = onSubmit; } @@ -547,9 +574,11 @@ class MDInput implements OptionsElement { div.append(document.createElement("br")); const input = document.createElement("textarea"); input.value = this.value; + input.maxLength = this.maxLength; input.oninput = this.onChange.bind(this); this.input = new WeakRef(input); div.append(input); + this.html_div = div; return div; } onChange() { @@ -559,6 +588,10 @@ class MDInput implements OptionsElement { const value = input.value as string; this.onchange(value); this.value = value; + if (value.length == this.maxLength) {; + let limit = " (" + this.maxLength.toString() + ")"; + this.makeError(I18n.localuser.inputLengthLimit() + limit); + } } } onchange: (str: string) => void = (_) => {}; @@ -568,6 +601,21 @@ class MDInput implements OptionsElement { submit() { this.onSubmit(this.value); } + makeError(message: string) { + let element = this.html_div.getElementsByClassName("suberror")[0] as HTMLElement; + if (!element) { + const div = document.createElement("div"); + div.classList.add("suberror", "suberrora"); + this.html_div.append(div); + element = div; + } else { + element.classList.remove("suberror"); + setTimeout((_) => { + element.classList.add("suberror"); + }, 100); + } + element.textContent = message; + } } class EmojiInput implements OptionsElement { readonly label: string; @@ -1195,11 +1243,12 @@ class Options implements OptionsElement { addTextInput( label: string, onSubmit: (str: string) => void, - {initText = "", password = false} = {}, + {initText = "", password = false, maxLength=9999} = {}, ) { const textInput = new TextInput(label, onSubmit, this, { initText, password, + maxLength, }); this.options.push(textInput); this.generate(textInput); @@ -1211,8 +1260,8 @@ class Options implements OptionsElement { this.generate(colorInput); return colorInput; } - addMDInput(label: string, onSubmit: (str: string) => void, {initText = ""} = {}) { - const mdInput = new MDInput(label, onSubmit, this, {initText}); + addMDInput(label: string, onSubmit: (str: string) => void, {initText = "", maxLength=9999} = {}) { + const mdInput = new MDInput(label, onSubmit, this, {initText, maxLength}); this.options.push(mdInput); this.generate(mdInput); return mdInput; @@ -1803,11 +1852,12 @@ class Form implements OptionsElement { addTextInput( label: string, formName: string, - {initText = "", required = false, password = false} = {}, + {initText = "", required = false, password = false, maxLength=9999} = {}, ) { const textInput = this.options.addTextInput(label, (_) => {}, { initText, password, + maxLength, }); this.names.set(formName, textInput); if (required) { @@ -1826,8 +1876,8 @@ class Form implements OptionsElement { return colorInput; } - addMDInput(label: string, formName: string, {initText = "", required = false} = {}) { - const mdInput = this.options.addMDInput(label, (_) => {}, {initText}); + addMDInput(label: string, formName: string, {initText = "", required = false, maxLength=9999} = {}) { + const mdInput = this.options.addMDInput(label, (_) => {}, {initText, maxLength}); this.names.set(formName, mdInput); if (required) { this.required.add(mdInput); diff --git a/src/webpage/user.ts b/src/webpage/user.ts index ef786512..485d0868 100644 --- a/src/webpage/user.ts +++ b/src/webpage/user.ts @@ -640,6 +640,7 @@ class User extends SnowFlake { ); form.addTextInput(I18n.member["nick:"](), "nickname", { initText: this.nickname || "", + maxLength: this.localuser.instanceLimits.user?.maxUsername ?? 9999, }); dio.show(); } diff --git a/translations/en.json b/translations/en.json index 84d6840d..45654f15 100644 --- a/translations/en.json +++ b/translations/en.json @@ -484,6 +484,7 @@ "areYouSureDelete": "Are you sure you want to delete your account? If so enter the phrase $1", "badCode": "Invalid code", "badPassword": "Incorrect password", + "inputLengthLimit": "Length limit reached", "botAvatar": "Bot avatar:", "botInviteCreate": "Bot Invite Creator", "botUsername": "Bot username:",