Compare commits

...

7 Commits

Author SHA1 Message Date
Abhinav Raut
879c626fb3 fix incorrect sender name when there are multiple participants involved 2025-11-03 15:56:23 +05:30
Abhinav Raut
16fbfa7b7c fix: display message content conditionally based on content type
Show `text` as is, render HTML with vue-letter
2025-11-03 13:11:53 +05:30
Abhinav Raut
b8da96c1d1 fix: set Content-Disposition header for media served from filesystem
set inline for videos, images and pdfs and attachment for rest
2025-11-03 13:11:27 +05:30
Abhinav Raut
3d76cce66a Merge pull request #151 from csr4422/enhancement/relative-time-months
Add month support to getRelativeTime
2025-10-03 13:39:14 +05:30
csr4422
4b8f30184a style: adjust spacing 2025-10-03 13:09:00 +05:30
csr4422
e4018ddab8 Add month support to getRelativeTime 2025-10-01 16:22:02 +05:30
Abhinav Raut
02e8a43587 Update README.md 2025-09-30 14:45:51 +05:30
4 changed files with 34 additions and 12 deletions

View File

@@ -83,11 +83,6 @@ __________________
## Developers ## Developers
If you are interested in contributing, refer to the [developer setup](https://docs.libredesk.io/contributing/developer-setup). The backend is written in Go and the frontend is Vue js 3 with Shadcn for UI components. If you are interested in contributing, refer to the [developer setup](https://docs.libredesk.io/contributing/developer-setup). The backend is written in Go and the frontend is Vue js 3 with Shadcn for UI components.
## Development Status
Libredesk is under active development.
Track roadmap and progress on the GitHub Project Board: [https://github.com/users/abhinavxd/projects/1](https://github.com/users/abhinavxd/projects/1)
## Translators ## Translators
You can help translate Libredesk into your language on [Crowdin](https://crowdin.com/project/libredesk). You can help translate Libredesk into your language on [Crowdin](https://crowdin.com/project/libredesk).

View File

@@ -185,6 +185,18 @@ func handleServeMedia(r *fastglue.Request) error {
consts := app.consts.Load().(*constants) consts := app.consts.Load().(*constants)
switch consts.UploadProvider { switch consts.UploadProvider {
case "fs": case "fs":
disposition := "attachment"
// Keep certain content types inline.
if strings.HasPrefix(media.ContentType, "image/") ||
strings.HasPrefix(media.ContentType, "video/") ||
media.ContentType == "application/pdf" {
disposition = "inline"
}
r.RequestCtx.Response.Header.Set("Content-Type", media.ContentType)
r.RequestCtx.Response.Header.Set("Content-Disposition", fmt.Sprintf(`%s; filename="%s"`, disposition, media.Filename))
fasthttp.ServeFile(r.RequestCtx, filepath.Join(ko.String("upload.fs.upload_path"), uuid)) fasthttp.ServeFile(r.RequestCtx, filepath.Join(ko.String("upload.fs.upload_path"), uuid))
case "s3": case "s3":
r.RequestCtx.Redirect(app.media.GetURL(uuid), http.StatusFound) r.RequestCtx.Redirect(app.media.GetURL(uuid), http.StatusFound)

View File

@@ -31,7 +31,15 @@
<hr class="mb-2" v-if="showEnvelope" /> <hr class="mb-2" v-if="showEnvelope" />
<!-- Message Text --> <!-- Message Text -->
<div
v-if="message.content_type === 'text'"
class="mb-1 native-html break-all whitespace-pre-wrap"
:class="{ 'mb-3': message.attachments.length > 0 }"
>
{{ sanitizedMessageContent }}
</div>
<Letter <Letter
v-else
:html="sanitizedMessageContent" :html="sanitizedMessageContent"
:allowedSchemas="['cid', 'https', 'http', 'mailto']" :allowedSchemas="['cid', 'https', 'http', 'mailto']"
class="mb-1 native-html break-all" class="mb-1 native-html break-all"
@@ -94,8 +102,12 @@ const settingsStore = useAppSettingsStore()
const showQuotedText = ref(false) const showQuotedText = ref(false)
const { t } = useI18n() const { t } = useI18n()
const participant = computed(() => {
return convStore.conversation?.participants?.[props.message.sender_id] ?? {}
})
const getAvatar = computed(() => { const getAvatar = computed(() => {
return convStore.current?.contact?.avatar_url || '' return participant.value?.avatar_url || ''
}) })
const sanitizedMessageContent = computed(() => { const sanitizedMessageContent = computed(() => {
let content = props.message.content || '' let content = props.message.content || ''
@@ -124,13 +136,14 @@ const nonInlineAttachments = computed(() =>
) )
const getFullName = computed(() => { const getFullName = computed(() => {
const contact = convStore.current?.contact || {} const firstName = participant.value?.first_name ?? 'User'
return `${contact.first_name || ''} ${contact.last_name || ''}`.trim() const lastName = participant.value?.last_name ?? ''
return `${firstName} ${lastName}`
}) })
const avatarFallback = computed(() => { const avatarFallback = computed(() => {
const contact = convStore.current?.contact || {} const firstName = participant.value?.first_name ?? 'U'
return (contact.first_name || '').toUpperCase().substring(0, 2) return firstName.toUpperCase().substring(0, 2)
}) })
const showEnvelope = computed(() => { const showEnvelope = computed(() => {

View File

@@ -1,16 +1,18 @@
import { format, differenceInMinutes, differenceInHours, differenceInDays, differenceInYears } from 'date-fns' import { format, differenceInMinutes, differenceInHours, differenceInDays, differenceInMonths, differenceInYears } from 'date-fns'
export function getRelativeTime (timestamp, now = new Date()) { export function getRelativeTime (timestamp, now = new Date()) {
try { try {
const mins = differenceInMinutes(now, timestamp) const mins = differenceInMinutes(now, timestamp)
const hours = differenceInHours(now, timestamp) const hours = differenceInHours(now, timestamp)
const days = differenceInDays(now, timestamp) const days = differenceInDays(now, timestamp)
const months = differenceInMonths(now, timestamp)
const years = differenceInYears(now, timestamp) const years = differenceInYears(now, timestamp)
if (mins === 0) return 'now' if (mins === 0) return 'now'
if (mins < 60) return `${mins}m` if (mins < 60) return `${mins}m`
if (hours < 24) return `${hours}h` if (hours < 24) return `${hours}h`
if (days < 365) return `${days}d` if (days < 31) return `${days}d`
if (months < 12) return `${months}mo`
return `${years}y` return `${years}y`
} catch (error) { } catch (error) {
console.error('Error parsing time', error, 'timestamp', timestamp) console.error('Error parsing time', error, 'timestamp', timestamp)