Compare commits

...

16 commits

Author SHA1 Message Date
a38589615b
chore: finnish merge
All checks were successful
ci/woodpecker/push/build-and-publish Pipeline was successful
ci/woodpecker/pr/build-and-publish Pipeline was successful
2025-06-02 13:14:08 +02:00
cb1979a941
Merge branch 'settings-page' of ssh://git.gorb.app:2022/gorb/frontend into settings-page 2025-06-02 13:13:44 +02:00
acc4fa14b7
feat: finish up the my account section for now 2025-06-02 13:10:42 +02:00
8a3bb89f8a
Merge branch 'main' into settings-page 2025-06-02 12:36:20 +02:00
4b1f1266b0
fix: type hinting 2025-06-02 12:36:04 +02:00
76952922bf
feat: add tabbing for channels list, members list, and messages
All checks were successful
ci/woodpecker/push/build-and-publish Pipeline was successful
2025-06-02 11:40:57 +02:00
c7e7f33240
feat: add sanitized ref variable
All checks were successful
ci/woodpecker/push/build-and-publish Pipeline was successful
2025-06-02 11:06:08 +02:00
2008033216
feat: add DOMPurify and marked packages
All checks were successful
ci/woodpecker/push/build-and-publish Pipeline was successful
2025-06-02 11:03:54 +02:00
63b780e5ab
feat: implement basic markdown for messages
Some checks failed
ci/woodpecker/push/build-and-publish Pipeline failed
2025-06-02 00:57:13 +02:00
35852d8cad
feat: make URLs in messages become links
All checks were successful
ci/woodpecker/push/build-and-publish Pipeline was successful
2025-06-01 23:10:08 +02:00
6182e00dd9
feat: decrease top padding of members container
All checks were successful
ci/woodpecker/push/build-and-publish Pipeline was successful
2025-06-01 17:38:03 +02:00
e8d37af75e
feat: update appearance of members list
All checks were successful
ci/woodpecker/push/build-and-publish Pipeline was successful
2025-06-01 17:35:36 +02:00
532aba5c21
feat: merge two #member-list blocks 2025-06-01 17:03:19 +02:00
f6523ae97f
feat: remove unnecessary styles from client layout 2025-06-01 17:02:58 +02:00
626c1c8453
fix: older messages loading in reverse order
All checks were successful
ci/woodpecker/push/build-and-publish Pipeline was successful
2025-06-01 16:58:53 +02:00
82796377ee
feat: change non-grouped messages to have margin-top instead of margin-bottom
All checks were successful
ci/woodpecker/push/build-and-publish Pipeline was successful
2025-06-01 16:57:56 +02:00
11 changed files with 147 additions and 126 deletions

View file

@ -1,11 +1,11 @@
<template> <template>
<div v-if="isCurrentChannel" class="channel-list-link-container rounded-corners current-channel"> <div v-if="isCurrentChannel" class="channel-list-link-container rounded-corners current-channel" tabindex="0">
<NuxtLink class="channel-list-link" :href="props.href"> <NuxtLink class="channel-list-link" :href="props.href" tabindex="-1">
# {{ props.name }} # {{ props.name }}
</NuxtLink> </NuxtLink>
</div> </div>
<div v-else class="channel-list-link-container rounded-corners"> <div v-else class="channel-list-link-container rounded-corners" tabindex="0">
<NuxtLink class="channel-list-link" :href="props.href"> <NuxtLink class="channel-list-link" :href="props.href" tabindex="-1">
# {{ props.name }} # {{ props.name }}
</NuxtLink> </NuxtLink>
</div> </div>

View file

@ -1,5 +1,5 @@
<template> <template>
<div v-if="props.type == 'normal'" :id="props.last ? 'last-message' : undefined" class="message normal-message" :class="{ 'message-margin-bottom': props.marginBottom }"> <div v-if="props.type == 'normal'" :id="props.last ? 'last-message' : undefined" class="message normal-message" :class="{ 'message-margin-bottom': props.marginBottom }" tabindex="0">
<div class="left-column"> <div class="left-column">
<img v-if="props.img" class="message-author-avatar" :src="props.img" :alt="username"> <img v-if="props.img" class="message-author-avatar" :src="props.img" :alt="username">
<Icon v-else name="lucide:user" class="message-author-avatar" /> <Icon v-else name="lucide:user" class="message-author-avatar" />
@ -13,12 +13,10 @@
{{ messageDate }} {{ messageDate }}
</span> </span>
</div> </div>
<div class="message-text"> <div class="message-text" v-html="sanitized"></div>
{{ text }}
</div>
</div> </div>
</div> </div>
<div v-else ref="messageElement" :id="props.last ? 'last-message' : undefined" class="message grouped-message"> <div v-else ref="messageElement" :id="props.last ? 'last-message' : undefined" class="message grouped-message" tabindex="0">
<div class="left-column"> <div class="left-column">
<div> <div>
<span :class="{ 'invisible': dateHidden }" class="message-date" :title="date.toString()"> <span :class="{ 'invisible': dateHidden }" class="message-date" :title="date.toString()">
@ -27,14 +25,15 @@
</div> </div>
</div> </div>
<div class="message-data"> <div class="message-data">
<div class="message-text"> <div class="message-text" v-html="sanitized"></div>
{{ text }}
</div>
</div> </div>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import DOMPurify from 'dompurify';
import { parseInline } from 'marked';
const props = defineProps<{ const props = defineProps<{
class?: string, class?: string,
img?: string | null, img?: string | null,
@ -74,7 +73,11 @@ if (props.format == "12") {
console.log("message:", props.text); console.log("message:", props.text);
console.log("author:", props.username); console.log("author:", props.username);
const sanitized = ref<string>();
onMounted(async () => { onMounted(async () => {
const parsed = await parseInline(props.text, {gfm: true });
sanitized.value = DOMPurify.sanitize(parsed, { ALLOWED_TAGS: ["strong", "em", "br", "blockquote", "code", "ul", "ol", "li", "a"] });
console.log("adding listeners") console.log("adding listeners")
await nextTick(); await nextTick();
messageElement.value?.addEventListener("mouseenter", (e: Event) => { messageElement.value?.addEventListener("mouseenter", (e: Event) => {
@ -102,8 +105,8 @@ onMounted(async () => {
align-items: center; align-items: center;
} }
.message-margin-bottom { .normal-message {
margin-bottom: 1dvh; margin-top: 1dvh;
} }
#last-message { #last-message {

View file

@ -185,6 +185,7 @@ onMounted(async () => {
olderMessages.reverse(); olderMessages.reverse();
console.log("older messages:", olderMessages); console.log("older messages:", olderMessages);
if (olderMessages.length == 0) return; if (olderMessages.length == 0) return;
olderMessages.reverse();
for (const [i, oldMessage] of olderMessages.entries()) { for (const [i, oldMessage] of olderMessages.entries()) {
console.log("old message:", oldMessage); console.log("old message:", oldMessage);
messages.value.unshift(oldMessage); messages.value.unshift(oldMessage);

View file

@ -10,7 +10,7 @@
{{ props.user.username || "username" }} - {{ props.user.pronouns || "un/defined" }} {{ props.user.username || "username" }} - {{ props.user.pronouns || "un/defined" }}
</p> </p>
<div id="about-me"> <div id="about-me">
<span>About me</span> {{ props.user.about || "about me" }}
</div> </div>
</div> </div>
</div> </div>
@ -74,8 +74,11 @@ const props = defineProps<{
} }
#about-me { #about-me {
margin-top: 4px; background-color: #34200f;
padding: 4px; border-radius: 12px;
margin-top: 32px;
padding: 16px;
font-size: 16px; font-size: 16px;
} }
</style> </style>

View file

@ -2,21 +2,24 @@
<div> <div>
<input id="hidden-pfp-uploader" type="file" accept="image/*" style="display: none;"> <input id="hidden-pfp-uploader" type="file" accept="image/*" style="display: none;">
<h1>My Account</h1> <h1>My Account</h1>
<div class="profile-and-user-data-fields"> <div class="profile-and-user-data-fields">
<div class="user-data-fields"> <div class="user-data-fields">
<h3 class="subtitle">AVATAR</h3> <h3 class="subtitle">AVATAR</h3>
<Button text="Change Avatar" :callback="changeAvatar"></Button> <Button text="Change Avatar" :callback="changeAvatar"></Button>
<Button text="Remove Avatar" :callback="removeAvatar" style="margin-left: 10px; background-color: grey;"></Button> <Button text="Remove Avatar" :callback="removeAvatar"
style="margin-left: 10px; background-color: grey;"></Button>
<h3 class="subtitle">DISPLAY NAME</h3> <h3 class="subtitle">DISPLAY NAME</h3>
<input type="text" v-model="user.display_name" placeholder="Enter display name" /> <input class="profile-data-input" type="text" v-model="user.display_name" placeholder="Enter display name" />
<h3 class="subtitle">USERNAME</h3> <h3 class="subtitle">USERNAME</h3>
<input type="text" v-model="user.username" placeholder="Enter username" /> <input class="profile-data-input" type="text" v-model="user.username" placeholder="Enter username" />
<h3 class="subtitle">PRONOUNS</h3> <h3 class="subtitle">PRONOUNS</h3>
<input type="text" v-model="user.pronouns" placeholder="Enter pronouns" /> <input class="profile-data-input" type="text" v-model="user.pronouns" placeholder="Enter pronouns" />
<h3 class="subtitle">ABOUT ME</h3> <h3 class="subtitle">ABOUT ME</h3>
<p>{{ user?.about_me || "TBD" }}</p> <input class="profile-data-input" type="text" v-model="user.about" placeholder="About You" />
<br>
<br>
<Button text="Save Changes" :callback="saveChanges"></Button> <Button text="Save Changes" :callback="saveChanges"></Button>
</div> </div>
<Userpopup :user=user_me class="profile"></Userpopup> <Userpopup :user=user_me class="profile"></Userpopup>
@ -29,10 +32,10 @@
<br> <br>
<h2>Password (and eventually authenticator)</h2> <h2>Password (and eventually authenticator)</h2>
<Button text="Reset Password (tbd)" :callback=resetPassword></Button> <Button text="Reset Password (tbd)" :callback=resetPassword></Button>
<h2>Account Deletion</h2> <h2>Account Deletion</h2>
<ButtonScary text="Delete Account (tbd)" :callback=deleteAccount></ButtonScary> <ButtonScary text="Delete Account (tbd)" :callback=deleteAccount></ButtonScary>
</div> </div>
</template> </template>
@ -50,38 +53,33 @@ const user = user_me!
let new_pfp_file: any = null let new_pfp_file: any = null
const saveChanges = async () => { const saveChanges = async () => {
const formData = new FormData()
if (new_pfp_file !== null) {
formData.append("avatar", new_pfp_file)
}
const bytes = new TextEncoder().encode(JSON.stringify({
display_name: user.display_name,
username: user.username,
pronouns: user.pronouns,
about: user.about,
}));
formData.append('json', new Blob([bytes], { type: 'application/json' }));
try { try {
const formData = new FormData() await fetchWithApi('/me', {
method: 'PATCH',
const pfpInput = document.getElementById("hidden-pfp-uploader") as HTMLInputElement | null;
if (pfpInput) {
if (pfpInput.files?.length && pfpInput.files.length > 0) {
console.log(pfpInput.files[0])
formData.append("avatar", pfpInput.files[0])
}
}
const bytes = new TextEncoder().encode(JSON.stringify({
display_name: user.display_name,
username: user.username,
pronouns: user.pronouns,
}));
formData.append("json", new Blob([bytes], { type: "application/json" }));
await fetchWithApi("/me", {
method: "PATCH",
body: formData body: formData
}) })
user_reference = Object.assign({}, user_me) user_reference = Object.assign({}, await fetchUser())
alert("success!!") alert('success!!')
} catch (error: any) { } catch (error: any) {
if (error?.response?.status !== 200) { if (error?.response?.status !== 200) {
const errorData = await error?.response?.json() alert(`error ${error?.response?.status} met whilst trying to update profile info`)
alert(`error ${error?.response?.status} met whilst trying to update profile info\n${errorData}`)
} }
} }
}; };
@ -91,23 +89,28 @@ const removeAvatar = async () => {
} }
const changeAvatar = async () => { const changeAvatar = async () => {
const pfpInput = document.getElementById("hidden-pfp-uploader") as HTMLInputElement | null; let input = document.createElement('input');
input.type = 'file';
input.accept = 'image/*';
// upload_field.onchange = async(e) => { input.onchange = async (e) => {
// console.log(upload_field.files) if (input.files?.length && input.files.length > 0) {
// if (upload_field.files?.length && upload_field.files.length > 0) { const file = input.files[0];
// const file = upload_field.files[0]; if (!file) return;
// if (!file) return;
// const reader = new FileReader(); new_pfp_file = file
// reader.onload = (e) => {
// user.avatar = e?.target?.result; const reader = new FileReader();
// }; reader.onload = (e) => {
// reader.readAsDataURL(file); if (e.target?.result && typeof e.target.result === 'string') {
// } user.avatar = e.target.result;
// } }
};
reader.readAsDataURL(file);
}
}
pfpInput?.click() input.click()
} }
const resetPassword = async () => { const resetPassword = async () => {
@ -132,7 +135,8 @@ const deleteAccount = async () => {
display: flex; display: flex;
} }
.profile-container, .user-data-fields { .profile-container,
.user-data-fields {
min-width: 350px; min-width: 350px;
} }
@ -141,4 +145,16 @@ const deleteAccount = async () => {
font-weight: 800; font-weight: 800;
margin: 12px 0; margin: 12px 0;
} }
.profile-data-input {
min-width: 300px;
margin: 2px;
padding: 2px 10px;
height: 40px;
font-size: 16px;
border-radius: 8px;
border: none;
color: white;
background-color: #54361b;
}
</style> </style>

View file

@ -123,30 +123,6 @@ const members = [
grid-row: 1; grid-row: 1;
} }
#test {
grid-column: 3;
grid-row: 1;
}
.member-item {
display: flex;
justify-content: center;
align-items: center;
}
#message-history,
#members-list {
padding-top: 3dvh;
}
#message-history {
display: flex;
flex-direction: column;
justify-content: space-between;
padding-left: 3dvw;
padding-right: 3dvw;
}
#left-column { #left-column {
display: flex; display: flex;
flex-direction: column; flex-direction: column;

View file

@ -14,6 +14,8 @@
"@nuxt/icon": "1.13.0", "@nuxt/icon": "1.13.0",
"@nuxt/image": "1.10.0", "@nuxt/image": "1.10.0",
"@pinia/nuxt": "0.11.0", "@pinia/nuxt": "0.11.0",
"dompurify": "^3.2.6",
"marked": "^15.0.12",
"nuxt": "^3.17.0", "nuxt": "^3.17.0",
"pinia": "^3.0.2", "pinia": "^3.0.2",
"pinia-plugin-persistedstate": "^4.2.0", "pinia-plugin-persistedstate": "^4.2.0",

View file

@ -24,11 +24,13 @@
</div> </div>
</div> </div>
<MessageArea :channel-url="channelUrlPath" /> <MessageArea :channel-url="channelUrlPath" />
<div id="members-list"> <div id="members-container">
<div class="member-item" v-for="member of members"> <div id="members-list">
<img v-if="member.user.avatar" class="member-avatar" :src="member.user.avatar" :alt="member.user.display_name ?? member.user.username" /> <div class="member-item" v-for="member of members" tabindex="0">
<Icon v-else class="member-avatar" name="lucide:user" /> <img v-if="member.user.avatar" class="member-avatar" :src="member.user.avatar" :alt="member.user.display_name ?? member.user.username" />
<span class="member-display-name">{{ member.user.display_name ?? member.user.username }}</span> <Icon v-else class="member-avatar" name="lucide:user" />
<span class="member-display-name">{{ member.user.display_name ?? member.user.username }}</span>
</div>
</div> </div>
</div> </div>
</NuxtLayout> </NuxtLayout>
@ -79,18 +81,6 @@ function toggleInvitePopup(e: Event) {
</script> </script>
<style> <style>
.member-item {
display: flex;
justify-content: center;
align-items: center;
margin-top: .5em;
margin-bottom: .5em;
gap: .5em;
}
#members-list {
padding-top: 3dvh;
}
#middle-left-column { #middle-left-column {
padding-left: 1dvw; padding-left: 1dvw;
@ -98,12 +88,29 @@ function toggleInvitePopup(e: Event) {
border-right: 1px solid rgb(70, 70, 70); border-right: 1px solid rgb(70, 70, 70);
} }
#members-list { #members-container {
padding-top: 1dvh;
padding-left: 1dvw; padding-left: 1dvw;
padding-right: 1dvw; padding-right: 1dvw;
border-left: 1px solid rgb(70, 70, 70); border-left: 1px solid rgb(70, 70, 70);
} }
#members-list {
display: grid;
grid-template-columns: auto;
}
.member-item {
display: grid;
grid-template-columns: 2dvw auto;
margin-top: .5em;
margin-bottom: .5em;
gap: 1em;
align-items: center;
text-align: left;
}
#channels-list { #channels-list {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -116,4 +123,9 @@ function toggleInvitePopup(e: Event) {
border-radius: 50%; border-radius: 50%;
} }
.member-display-name {
overflow: hidden;
text-overflow: ellipsis;
}
</style> </style>

View file

@ -69,24 +69,6 @@ const settingsCategories = {
{ display_name: "Language", page_data: Language }, { display_name: "Language", page_data: Language },
] ]
}, },
app_settings2: {
display_name: "App Settings",
pages: [
{ display_name: "Appearance", page_data: Appearance },
{ display_name: "Notifications", page_data: Notifications },
{ display_name: "Keybinds", page_data: Keybinds },
{ display_name: "Language", page_data: Language },
]
},
app_settings3: {
display_name: "App Settings",
pages: [
{ display_name: "Appearance", page_data: Appearance },
{ display_name: "Notifications", page_data: Notifications },
{ display_name: "Keybinds", page_data: Keybinds },
{ display_name: "Language", page_data: Language },
]
},
}; };
const categories = Object.values(settingsCategories); const categories = Object.values(settingsCategories);

26
pnpm-lock.yaml generated
View file

@ -20,6 +20,12 @@ importers:
'@pinia/nuxt': '@pinia/nuxt':
specifier: 0.11.0 specifier: 0.11.0
version: 0.11.0(magicast@0.3.5)(pinia@3.0.2(typescript@5.8.3)(vue@3.5.13(typescript@5.8.3))) version: 0.11.0(magicast@0.3.5)(pinia@3.0.2(typescript@5.8.3)(vue@3.5.13(typescript@5.8.3)))
dompurify:
specifier: ^3.2.6
version: 3.2.6
marked:
specifier: ^15.0.12
version: 15.0.12
nuxt: nuxt:
specifier: ^3.17.0 specifier: ^3.17.0
version: 3.17.0(@netlify/blobs@8.2.0)(@parcel/watcher@2.5.1)(@types/node@22.15.3)(db0@0.3.2)(eslint@9.25.1(jiti@2.4.2))(ioredis@5.6.1)(lightningcss@1.29.2)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.40.1)(terser@5.39.0)(typescript@5.8.3)(vite@6.3.3(@types/node@22.15.3)(jiti@2.4.2)(lightningcss@1.29.2)(terser@5.39.0)(yaml@2.7.1))(yaml@2.7.1) version: 3.17.0(@netlify/blobs@8.2.0)(@parcel/watcher@2.5.1)(@types/node@22.15.3)(db0@0.3.2)(eslint@9.25.1(jiti@2.4.2))(ioredis@5.6.1)(lightningcss@1.29.2)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.40.1)(terser@5.39.0)(typescript@5.8.3)(vite@6.3.3(@types/node@22.15.3)(jiti@2.4.2)(lightningcss@1.29.2)(terser@5.39.0)(yaml@2.7.1))(yaml@2.7.1)
@ -1199,6 +1205,9 @@ packages:
'@types/triple-beam@1.3.5': '@types/triple-beam@1.3.5':
resolution: {integrity: sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==} resolution: {integrity: sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==}
'@types/trusted-types@2.0.7':
resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==}
'@types/yauzl@2.10.3': '@types/yauzl@2.10.3':
resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==}
@ -2113,6 +2122,9 @@ packages:
resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==}
engines: {node: '>= 4'} engines: {node: '>= 4'}
dompurify@3.2.6:
resolution: {integrity: sha512-/2GogDQlohXPZe6D6NOgQvXLPSYBqIWMnZ8zzOhn09REE4eyAzb+Hed3jhoM9OkuaJ8P6ZGTTVWQKAi8ieIzfQ==}
domutils@3.2.2: domutils@3.2.2:
resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==}
@ -3092,6 +3104,11 @@ packages:
resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==}
engines: {node: '>=8'} engines: {node: '>=8'}
marked@15.0.12:
resolution: {integrity: sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==}
engines: {node: '>= 18'}
hasBin: true
math-intrinsics@1.1.0: math-intrinsics@1.1.0:
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@ -6094,6 +6111,9 @@ snapshots:
'@types/triple-beam@1.3.5': {} '@types/triple-beam@1.3.5': {}
'@types/trusted-types@2.0.7':
optional: true
'@types/yauzl@2.10.3': '@types/yauzl@2.10.3':
dependencies: dependencies:
'@types/node': 22.15.3 '@types/node': 22.15.3
@ -7082,6 +7102,10 @@ snapshots:
dependencies: dependencies:
domelementtype: 2.3.0 domelementtype: 2.3.0
dompurify@3.2.6:
optionalDependencies:
'@types/trusted-types': 2.0.7
domutils@3.2.2: domutils@3.2.2:
dependencies: dependencies:
dom-serializer: 2.0.0 dom-serializer: 2.0.0
@ -8170,6 +8194,8 @@ snapshots:
dependencies: dependencies:
semver: 6.3.1 semver: 6.3.1
marked@15.0.12: {}
math-intrinsics@1.1.0: {} math-intrinsics@1.1.0: {}
mdn-data@2.0.28: {} mdn-data@2.0.28: {}

View file

@ -59,7 +59,7 @@ export interface UserResponse {
display_name: string | null, display_name: string | null,
avatar: string | null, avatar: string | null,
pronouns: string | null, pronouns: string | null,
about_me: string | null, about: string | null,
email?: string, email?: string,
email_verified?: boolean email_verified?: boolean
} }