Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/Address.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public static function fromHorde(Horde_Mail_Rfc822_Address $horde): self {
}

public static function fromRaw(string $label, string $email): self {
$wrapped = new Horde_Mail_Rfc822_Address($email);
$wrapped = new Horde_Mail_Rfc822_Address(strtolower($email));
// If no label is set we use the email
if ($label !== $email) {
$wrapped->personal = $label;
Expand Down
6 changes: 3 additions & 3 deletions src/components/Composer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -1295,7 +1295,7 @@ export default {
|| account.name.toLowerCase().indexOf(term.toLowerCase()) !== -1,
)
.map(account => ({
email: account.emailAddress,
email: account.emailAddress.toLowerCase(),
label: account.name,
}))
this.autocompleteRecipients = uniqBy('email')(this.autocompleteRecipients.concat(selfRecipients))
Expand Down Expand Up @@ -1353,7 +1353,7 @@ export default {
return
}
option = {}
option.email = this.recipientSearchTerms[type]
option.email = this.recipientSearchTerms[type].toLowerCase()
option.label = this.recipientSearchTerms[type]
this.recipientSearchTerms[type] = ''
}
Expand Down Expand Up @@ -1523,7 +1523,7 @@ export default {
if (!this.seemsValidEmailAddress(value)) {
throw new Error('Skipping because it does not look like a valid email address')
}
return { email: value, label: value }
return { email: value.toLowerCase(), label: value }
},

/**
Expand Down
95 changes: 92 additions & 3 deletions src/components/Imip.vue
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,22 @@ function findAttendee(vEvent, email) {
return undefined
}

function findAttendeeByEmails(vEvent, emails) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey, we recently changed the logic with #11915. Please ensure to rebase your branch and also re-test if that is still necessary.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks

if (!vEvent || !Array.isArray(emails) || emails.length === 0) {
return undefined
}

const emailSet = new Set(emails.map(e => removeMailtoPrefix(e).toLowerCase()))
for (const attendee of [...vEvent.getPropertyIterator('ORGANIZER'), ...vEvent.getAttendeeIterator()]) {
const normalized = removeMailtoPrefix(attendee.email).toLowerCase()
if (emailSet.has(normalized)) {
return attendee
}
}

return undefined
}

export default {
name: 'Imip',
components: {
Expand All @@ -173,6 +189,10 @@ export default {
type: Object,
required: true,
},
message: {
type: Object,
required: true,
},
},
data() {
return {
Expand All @@ -197,6 +217,7 @@ export default {
currentUserPrincipalEmail: 'getCurrentUserPrincipalEmail',
clonedWriteableCalendars: 'getClonedWriteableCalendars',
currentUserPrincipal: 'getCurrentUserPrincipal',
accounts: 'getAccounts',
}),

/**
Expand All @@ -208,6 +229,14 @@ export default {
return this.scheduling.method
},

isFromDigikala() {
if (!this.message.from || !this.message.from[0]) {
return false
}
const fromEmail = this.message.from[0].email?.toLowerCase() || ''
return fromEmail.endsWith('@digikala.com')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please the purpose of this check?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We wanted to have a place to define trusted domains — so that whenever we receive a calendar event from one of those domains, it can automatically appear in the calendar as tentative.
In this case, this domain was just an example of one of our trusted sources.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While this fits your personal use case it needs a more generic approach to be usable for others too. How about changing this so it's based on trusted domains?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @ChristophWurst,

Thanks for the feedback! I’ve updated this part to make it more generic:

The trusted domains are now configurable via Admin Settings instead of being hardcoded.

The code reads the list of trusted domains from the settings, so any domain can be added and calendar events from those domains will appear automatically as tentative.

This makes the feature usable for all users, not just a single example domain.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The app already has trusted senders. That is the list of senders for which the app shows external images by default. Reuse that, please.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We tested the Trusted Senders feature, but there’s no button or visible option to add a trusted domain, even though we’re using the latest version of your app.

We also tried adding our domain (@digikala.com) directly to the database as admin, but that didn’t achieve what we’re aiming for — which is to have all emails from that domain automatically appear in the calendar.

It seems that the current trusted senders mechanism only affects image loading, not event handling.
Could you please guide us on how to properly achieve this behavior?

Thanks a lot for your help! 🙏

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. You could add a UI option to edit trusted senders.

The auto-import of trusted senders is also something you have to add.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Screenshot 2025-10-25 at 14 58 30 Screenshot 2025-10-25 at 15 02 06 As you can see in the screenshots, I’ve added the azarstan email from the database, and I can also see it in the panel under Trusted Senders. However, when an email is sent from azarstan, it doesn’t get added to the calendar.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ChristophWurst "Can we set up a time to chat on a platform so that we can solve our problem?"

},

/**
* @return {boolean}
*/
Expand Down Expand Up @@ -305,7 +334,7 @@ export default {
* @return {boolean}
*/
userIsAttendee() {
return !!findAttendee(this.attachedVEvent, this.currentUserPrincipalEmail)
return !!findAttendeeByEmails(this.attachedVEvent, this.allUserEmails)
},

/**
Expand All @@ -314,10 +343,38 @@ export default {
* @return {string|undefined}
*/
existingParticipationStatus() {
const attendee = findAttendee(this.existingVEvent, this.currentUserPrincipalEmail)
const attendee = findAttendeeByEmails(this.existingVEvent, this.allUserEmails)
return attendee?.participationStatus ?? undefined
},

/**
* All user's email addresses (principal + all mail account addresses and aliases)
*
* @return {string[]}
*/
allUserEmails() {
const emails = new Set()
if (this.currentUserPrincipalEmail) {
emails.add(this.currentUserPrincipalEmail.toLowerCase())
}
if (Array.isArray(this.accounts)) {
for (const account of this.accounts) {
if (account?.emailAddress) {
emails.add(String(account.emailAddress).toLowerCase())
}
if (Array.isArray(account?.aliases)) {
for (const alias of account.aliases) {
const address = alias?.alias || alias?.emailAddress
if (address) {
emails.add(String(address).toLowerCase())
}
}
}
}
}
return Array.from(emails)
},

/**
* The status message to show in case of REPLY messages.
*
Expand Down Expand Up @@ -352,6 +409,14 @@ export default {
})
},

existingEventFetched: {
immediate: false,
async handler(fetched) {
if (!fetched) return
await this.autoCreateTentativeIfNeeded()
},
},

/**
* List of calendar options for the target calendar picker.
*
Expand Down Expand Up @@ -410,6 +475,14 @@ export default {
},
},
},

async mounted() {
// If data already fetched on mount, attempt auto-create once
if (this.existingEventFetched) {
await this.autoCreateTentativeIfNeeded()
}
},

methods: {
async accept() {
await this.saveEventWithParticipationStatus(ACCEPTED)
Expand All @@ -420,6 +493,22 @@ export default {
async decline() {
await this.saveEventWithParticipationStatus(DECLINED)
},
async autoCreateTentativeIfNeeded() {
try {
if (
this.isRequest
&& !this.wasProcessed
&& this.userIsAttendee
&& this.eventIsInFuture
&& this.existingEventFetched
&& !this.isExistingEvent
) {
await this.saveEventWithParticipationStatus(TENTATIVE)
}
} catch (e) {
// ignore auto-create failures
}
},
async saveEventWithParticipationStatus(status) {
let vCalendar
if (this.isExistingEvent) {
Expand All @@ -428,7 +517,7 @@ export default {
vCalendar = this.attachedVCalendar
}
const vEvent = vCalendar.getFirstComponent('VEVENT')
const attendee = findAttendee(vEvent, this.currentUserPrincipalEmail)
const attendee = findAttendeeByEmails(vEvent, this.allUserEmails)
if (!attendee) {
return
}
Expand Down
4 changes: 3 additions & 1 deletion src/components/Message.vue
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@
<div v-if="hasCurrentUserPrincipalAndCollections && message.scheduling.length > 0" class="message-imip">
<Imip v-for="scheduling in message.scheduling"
:key="scheduling.id"
:scheduling="scheduling" />
:scheduling="scheduling"
:message="message"
/>
</div>
<MessageHTMLBody v-if="message.hasHtmlBody"
:url="htmlUrl"
Expand Down
2 changes: 1 addition & 1 deletion src/components/MessagePlainTextBody.vue
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export default {
computed: {
enhancedBody() {
return this.body.replace(/(^&gt;.*\n)+/gm, (match) => {
return `<details class="quoted-text"><summary>${t('mail', 'Quoted text')}</summary>${match}</details>`
return `<details class="quoted-text" open><summary>${t('mail', 'Quoted text')}</summary>${match}</details>`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This appears to be unrelated, no?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, it's not directly related. I noticed that in some cases incoming emails were not fully displayed — parts of the message body were being cut off. I added this change to fix that rendering issue and ensure the full email content is shown properly.

})
},
signatureSummaryAndBody() {
Expand Down