Skip to content
Open
Show file tree
Hide file tree
Changes from all 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: 6 additions & 0 deletions lib/Settings/AdminSettings.php
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,12 @@ public function getForm() {
$this->classificationSettingsService->isClassificationEnabledByDefault(),
);

$this->initialStateService->provideInitialState(
Application::APP_ID,
'digikala_domain',
$this->config->getAppValue('mail', 'digikala_domain', '@digikala.com'),
Copy link
Contributor

Choose a reason for hiding this comment

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

Hard coded domain names and vendor specific variables, are not a good solution

);

return new TemplateResponse(Application::APP_ID, 'settings-admin');
}

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
97 changes: 94 additions & 3 deletions src/components/Imip.vue
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ import { flatten } from 'ramda'
import { showError } from '@nextcloud/dialogs'
import useMainStore from '../store/mainStore.js'
import { mapState } from 'pinia'
import { loadState } from '@nextcloud/initial-state'

// iMIP methods
const REQUEST = 'REQUEST'
Expand Down Expand Up @@ -157,6 +158,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 +190,10 @@ export default {
type: Object,
required: true,
},
message: {
type: Object,
required: true,
},
},
data() {
return {
Expand All @@ -190,13 +211,15 @@ export default {
existingEventFetched: false,
targetCalendar: undefined,
comment: '',
digikalaDomain: loadState('mail', 'digikala_domain', '@digikala.com'),
Copy link
Contributor

Choose a reason for hiding this comment

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

Hard coded domain names and vendor specific variables, are not a good solution

}
},
computed: {
...mapState(useMainStore, {
currentUserPrincipalEmail: 'getCurrentUserPrincipalEmail',
clonedWriteableCalendars: 'getClonedWriteableCalendars',
currentUserPrincipal: 'getCurrentUserPrincipal',
accounts: 'getAccounts',
}),

/**
Expand All @@ -208,6 +231,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(this.digikalaDomain)
},

Comment on lines +234 to +241
Copy link
Contributor

Choose a reason for hiding this comment

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

vendor specific variables and method names, are not a good solution

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

/**
Expand All @@ -314,10 +345,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 +411,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 +477,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 +495,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 +519,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