generated from Nodarx/template
Merge pull request 'refactor: message properties' (#37) from refactor/message-properties into main
Reviewed-on: #37
This commit was merged in pull request #37.
This commit is contained in:
@@ -168,7 +168,7 @@ final class MessagePart
|
||||
$data = [
|
||||
'partId' => $this->partId,
|
||||
'blobId' => $this->partId,
|
||||
'cId' => $this->contentId,
|
||||
'cid' => $this->contentId,
|
||||
'type' => $this->mimeType,
|
||||
'charset' => $this->parameters['charset'] ?? null,
|
||||
'name' => $this->parameters['name'] ?? $this->dispositionParameters['filename'] ?? null,
|
||||
|
||||
@@ -121,10 +121,20 @@ class MessageAttachment implements MessagePartInterface {
|
||||
|
||||
public function getBlobId(): ?string { return $this->_meta->getBlobId(); }
|
||||
public function getId(): ?string { return $this->_meta->getId(); }
|
||||
public function getSize(): ?int { return $this->_meta->getSize(); }
|
||||
public function getDisposition(): ?string { return $this->_meta->getDisposition(); }
|
||||
public function getContentId(): ?string { return $this->_meta->getContentId(); }
|
||||
public function getCharset(): ?string { return $this->_meta->getCharset(); }
|
||||
public function getLanguage(): ?string { return $this->_meta->getLanguage(); }
|
||||
public function getLocation(): ?string { return $this->_meta->getLocation(); }
|
||||
public function getContent(): ?string { return $this->_contents; }
|
||||
public function getParts(): array { return $this->_meta->getParts(); }
|
||||
public function jsonSerialize(): array { return $this->_meta->jsonSerialize(); }
|
||||
public function jsonSerialize(): array {
|
||||
$data = $this->_meta->jsonSerialize();
|
||||
if ($this->_contents !== null) {
|
||||
$data['content'] = $this->_contents;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,24 @@ use KTXM\ProviderImap\Client\MessagePart as ImapMessagePart;
|
||||
*/
|
||||
class MessagePart extends MessagePartMutableAbstract {
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $data
|
||||
*/
|
||||
private function hydrateArray(array $data): static {
|
||||
$this->data = $data;
|
||||
|
||||
if (isset($this->data['subParts']) && is_array($this->data['subParts'])) {
|
||||
foreach ($this->data['subParts'] as $entry) {
|
||||
if (is_array($entry)) {
|
||||
$this->parts[] = (new self())->hydrateArray($entry);
|
||||
}
|
||||
}
|
||||
unset($this->data['subParts']);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert gricob BodyStructure part to message part object
|
||||
*
|
||||
@@ -24,82 +42,11 @@ class MessagePart extends MessagePartMutableAbstract {
|
||||
* @param string $partId numeric part identifier (e.g. "1", "1.1", "2")
|
||||
*/
|
||||
public function fromImap(ImapMessagePart $part, string $partId = '1'): static {
|
||||
$data = $part->toArray();
|
||||
$data['partId'] = $partId;
|
||||
$data['blobId'] = $data['blobId'] ?? $partId;
|
||||
|
||||
$this->data['partId'] = $partId;
|
||||
|
||||
if ($part instanceof SinglePart) {
|
||||
$mimeType = strtolower($part->type) . '/' . strtolower($part->subtype);
|
||||
$this->data['type'] = $mimeType;
|
||||
|
||||
if ($part->id !== null) {
|
||||
$this->data['blobId'] = trim($part->id, '<>');
|
||||
}
|
||||
|
||||
// Content-Type parameters (name, charset, etc.)
|
||||
if (!empty($part->attributes)) {
|
||||
foreach ($part->attributes as $key => $value) {
|
||||
$keyLower = strtolower($key);
|
||||
if ($keyLower === 'name') {
|
||||
$this->data['name'] = $value;
|
||||
} elseif ($keyLower === 'charset') {
|
||||
$this->data['charset'] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($part->encoding !== null) {
|
||||
$this->data['encoding'] = strtolower($part->encoding);
|
||||
}
|
||||
|
||||
if ($part->size !== null) {
|
||||
$this->data['size'] = $part->size;
|
||||
}
|
||||
|
||||
if ($part->disposition !== null) {
|
||||
$this->data['disposition'] = strtolower($part->disposition->type);
|
||||
// disposition filename attribute
|
||||
if (!empty($part->disposition->attributes)) {
|
||||
foreach ($part->disposition->attributes as $key => $value) {
|
||||
if (strtolower($key) === 'filename') {
|
||||
$this->data['name'] = $this->data['name'] ?? $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($part->language)) {
|
||||
$this->data['language'] = implode(',', $part->language);
|
||||
}
|
||||
|
||||
if ($part->location !== null) {
|
||||
$this->data['location'] = $part->location;
|
||||
}
|
||||
|
||||
} elseif ($part instanceof MultiPart) {
|
||||
$this->data['type'] = 'multipart/' . strtolower($part->subtype);
|
||||
|
||||
if ($part->disposition !== null) {
|
||||
$this->data['disposition'] = strtolower($part->disposition->type);
|
||||
}
|
||||
|
||||
if (!empty($part->language)) {
|
||||
$this->data['language'] = implode(',', $part->language);
|
||||
}
|
||||
|
||||
if ($part->location !== null) {
|
||||
$this->data['location'] = $part->location;
|
||||
}
|
||||
|
||||
// Recursively process sub-parts
|
||||
// When this part has no section ID (root multipart) children are
|
||||
// numbered "1", "2", … to match IMAP section numbering.
|
||||
foreach ($part->parts as $index => $subPart) {
|
||||
$subPartId = ($partId === '') ? (string)($index + 1) : $partId . '.' . ($index + 1);
|
||||
$this->parts[] = (new MessagePart())->fromImap($subPart, $subPartId);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
return $this->hydrateArray($data);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -82,36 +82,16 @@ class MessageProperties extends MessagePropertiesMutableAbstract {
|
||||
}
|
||||
|
||||
if ($message->bodyStructure() !== null) {
|
||||
$this->data[static::PROPERTY_BODY] = $message->bodyStructure()->toArray();
|
||||
$body = $message->bodyStructure()->withInjectedSections($message->bodySections() ?? [])->toArray();
|
||||
$this->data[static::PROPERTY_BODY] = $body;
|
||||
|
||||
$attachments = [];
|
||||
$this->collectAttachments($message->bodyStructure(), $attachments);
|
||||
$this->collectAttachments($body, $attachments);
|
||||
if ($attachments !== []) {
|
||||
$this->data[static::PROPERTY_ATTACHMENTS] = $attachments;
|
||||
}
|
||||
}
|
||||
|
||||
if ($message->bodyStructure() !== null) {
|
||||
$this->data[static::PROPERTY_BODY] = $message->bodyStructure()->toArray();
|
||||
// Recursively add content from bodyValues to matching parts
|
||||
if (is_array($message->bodySections())) {
|
||||
$addContentToParts = function(&$structure, $bodyValues) use (&$addContentToParts) {
|
||||
// If this part has a partId and matching bodyValue, add content
|
||||
if (isset($structure['partId']) && isset($bodyValues[$structure['partId']])) {
|
||||
$structure['content'] = $bodyValues[$structure['partId']] ?? null;
|
||||
}
|
||||
// Recursively process subParts
|
||||
if (isset($structure['subParts']) && is_array($structure['subParts'])) {
|
||||
foreach ($structure['subParts'] as &$subPart) {
|
||||
$addContentToParts($subPart, $bodyValues);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
$addContentToParts($this->data[static::PROPERTY_BODY], $message->bodySections());
|
||||
}
|
||||
}
|
||||
|
||||
$this->data[static::PROPERTY_FLAGS] = [];
|
||||
foreach ($message->flags() as $flag) {
|
||||
$flag = ltrim($flag, '\\');
|
||||
@@ -132,9 +112,9 @@ class MessageProperties extends MessagePropertiesMutableAbstract {
|
||||
/**
|
||||
* Recursively collect attachment parts from body structure
|
||||
*/
|
||||
private function collectAttachments(ClientMessagePart $part, array &$attachments): void
|
||||
private function collectAttachments(array $part, array &$attachments): void
|
||||
{
|
||||
$children = $part->parts();
|
||||
$children = $part['subParts'] ?? [];
|
||||
if ($children !== []) {
|
||||
foreach ($children as $childPart) {
|
||||
$this->collectAttachments($childPart, $attachments);
|
||||
@@ -142,9 +122,9 @@ class MessageProperties extends MessagePropertiesMutableAbstract {
|
||||
return;
|
||||
}
|
||||
|
||||
$mimeType = strtolower($part->mimeType());
|
||||
$disposition = strtolower($part->disposition() ?? '');
|
||||
$name = $part->parameters()['name'] ?? $part->dispositionParameters()['filename'] ?? null;
|
||||
$mimeType = strtolower((string)($part['type'] ?? ''));
|
||||
$disposition = strtolower((string)($part['disposition'] ?? ''));
|
||||
$name = $part['name'] ?? null;
|
||||
$isInlineText = str_starts_with($mimeType, 'text/')
|
||||
&& in_array($mimeType, ['text/plain', 'text/html'], true)
|
||||
&& $disposition !== 'attachment';
|
||||
@@ -153,7 +133,7 @@ class MessageProperties extends MessagePropertiesMutableAbstract {
|
||||
return;
|
||||
}
|
||||
|
||||
$attachments[] = $part->toArray();
|
||||
$attachments[] = $part;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -49,10 +49,10 @@ class Provider implements ProviderBaseInterface, ProviderServiceMutateInterface,
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return [
|
||||
self::JSON_PROPERTY_TYPE => self::JSON_TYPE,
|
||||
self::JSON_PROPERTY_IDENTIFIER => self::PROVIDER_IDENTIFIER,
|
||||
self::JSON_PROPERTY_LABEL => self::PROVIDER_LABEL,
|
||||
self::JSON_PROPERTY_CAPABILITIES => $this->providerAbilities,
|
||||
self::PROPERTY_TYPE => self::JSON_TYPE,
|
||||
self::PROPERTY_IDENTIFIER => self::PROVIDER_IDENTIFIER,
|
||||
self::PROPERTY_LABEL => self::PROVIDER_LABEL,
|
||||
self::PROPERTY_CAPABILITIES => $this->providerAbilities,
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
+38
-35
@@ -44,9 +44,6 @@ use KTXF\Resource\Identifier\EntityIdentifierInterface;
|
||||
use KTXM\ProviderImap\Providers\EntityResource;
|
||||
use KTXM\ProviderImap\Client\Mailbox;
|
||||
|
||||
/**
|
||||
* IMAP Mail Service
|
||||
*/
|
||||
class Service implements ServiceBaseInterface, ServiceMutableInterface, ServiceConfigurableInterface, ServiceCollectionMutableInterface, ServiceEntityMutableInterface
|
||||
{
|
||||
private const PROVIDER_IDENTIFIER = 'imap';
|
||||
@@ -56,7 +53,7 @@ class Service implements ServiceBaseInterface, ServiceMutableInterface, ServiceC
|
||||
private ?string $serviceIdentifier = null;
|
||||
private ?string $serviceLabel = null;
|
||||
private bool $serviceEnabled = false;
|
||||
private string $primaryAddress = '';
|
||||
private array $primaryAddress = [];
|
||||
private array $secondaryAddresses = [];
|
||||
private ?ServiceLocation $location = null;
|
||||
private ?ServiceIdentityBasic $identity = null;
|
||||
@@ -109,6 +106,7 @@ class Service implements ServiceBaseInterface, ServiceMutableInterface, ServiceC
|
||||
self::CAPABILITY_ENTITY_DELETE => true,
|
||||
self::CAPABILITY_ENTITY_MOVE => true,
|
||||
self::CAPABILITY_ENTITY_COPY => false,
|
||||
'EntityTransmit' => true,
|
||||
];
|
||||
|
||||
private RemoteMailService $remoteService;
|
||||
@@ -169,17 +167,17 @@ class Service implements ServiceBaseInterface, ServiceMutableInterface, ServiceC
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return array_filter([
|
||||
self::JSON_PROPERTY_TYPE => self::JSON_TYPE,
|
||||
self::JSON_PROPERTY_PROVIDER => self::PROVIDER_IDENTIFIER,
|
||||
self::JSON_PROPERTY_IDENTIFIER => $this->serviceIdentifier,
|
||||
self::JSON_PROPERTY_LABEL => $this->serviceLabel,
|
||||
self::JSON_PROPERTY_ENABLED => $this->serviceEnabled,
|
||||
self::JSON_PROPERTY_CAPABILITIES => $this->serviceAbilities,
|
||||
self::JSON_PROPERTY_PRIMARY_ADDRESS => $this->primaryAddress,
|
||||
self::JSON_PROPERTY_SECONDARY_ADDRESSES => $this->secondaryAddresses,
|
||||
self::JSON_PROPERTY_LOCATION => $this->location?->jsonSerialize(),
|
||||
self::JSON_PROPERTY_IDENTITY => $this->identity?->jsonSerialize(),
|
||||
self::JSON_PROPERTY_AUXILIARY => $this->auxiliary,
|
||||
self::PROPERTY_TYPE => self::JSON_TYPE,
|
||||
self::PROPERTY_PROVIDER => self::PROVIDER_IDENTIFIER,
|
||||
self::PROPERTY_IDENTIFIER => $this->serviceIdentifier,
|
||||
self::PROPERTY_LABEL => $this->serviceLabel,
|
||||
self::PROPERTY_ENABLED => $this->serviceEnabled,
|
||||
self::PROPERTY_CAPABILITIES => $this->serviceAbilities,
|
||||
self::PROPERTY_PRIMARY_ADDRESS => $this->primaryAddress,
|
||||
self::PROPERTY_SECONDARY_ADDRESSES => $this->secondaryAddresses,
|
||||
self::PROPERTY_LOCATION => $this->location?->jsonSerialize(),
|
||||
self::PROPERTY_IDENTITY => $this->identity?->jsonSerialize(),
|
||||
self::PROPERTY_AUXILIARY => $this->auxiliary,
|
||||
], fn($v) => $v !== null);
|
||||
}
|
||||
|
||||
@@ -189,29 +187,29 @@ class Service implements ServiceBaseInterface, ServiceMutableInterface, ServiceC
|
||||
$data = json_decode($data, true, 512, JSON_THROW_ON_ERROR);
|
||||
}
|
||||
|
||||
if (isset($data[self::JSON_PROPERTY_ENABLED])) {
|
||||
$this->setEnabled($data[self::JSON_PROPERTY_ENABLED]);
|
||||
if (isset($data[self::PROPERTY_ENABLED])) {
|
||||
$this->setEnabled($data[self::PROPERTY_ENABLED]);
|
||||
}
|
||||
if (isset($data[self::JSON_PROPERTY_LABEL])) {
|
||||
$this->setLabel($data[self::JSON_PROPERTY_LABEL]);
|
||||
if (isset($data[self::PROPERTY_LABEL])) {
|
||||
$this->setLabel($data[self::PROPERTY_LABEL]);
|
||||
}
|
||||
if (isset($data[self::JSON_PROPERTY_LOCATION])) {
|
||||
$this->setLocation($this->freshLocation(null, $data[self::JSON_PROPERTY_LOCATION]));
|
||||
if (isset($data[self::PROPERTY_LOCATION])) {
|
||||
$this->setLocation($this->freshLocation(null, $data[self::PROPERTY_LOCATION]));
|
||||
}
|
||||
if (isset($data[self::JSON_PROPERTY_IDENTITY])) {
|
||||
$this->setIdentity($this->freshIdentity(null, $data[self::JSON_PROPERTY_IDENTITY]));
|
||||
if (isset($data[self::PROPERTY_IDENTITY])) {
|
||||
$this->setIdentity($this->freshIdentity(null, $data[self::PROPERTY_IDENTITY]));
|
||||
}
|
||||
if (isset($data[self::JSON_PROPERTY_PRIMARY_ADDRESS]) && is_string($data[self::JSON_PROPERTY_PRIMARY_ADDRESS])) {
|
||||
$this->setPrimaryAddress(new Address($data[self::JSON_PROPERTY_PRIMARY_ADDRESS]));
|
||||
if (isset($data[self::PROPERTY_PRIMARY_ADDRESS]) && is_string($data[self::PROPERTY_PRIMARY_ADDRESS])) {
|
||||
$this->setPrimaryAddress(new Address($data[self::PROPERTY_PRIMARY_ADDRESS]));
|
||||
}
|
||||
if (isset($data[self::JSON_PROPERTY_SECONDARY_ADDRESSES]) && is_array($data[self::JSON_PROPERTY_SECONDARY_ADDRESSES])) {
|
||||
if (isset($data[self::PROPERTY_SECONDARY_ADDRESSES]) && is_array($data[self::PROPERTY_SECONDARY_ADDRESSES])) {
|
||||
$this->setSecondaryAddresses(array_map(
|
||||
fn($addr) => new Address(is_array($addr) ? ($addr['address'] ?? $addr) : $addr),
|
||||
$data[self::JSON_PROPERTY_SECONDARY_ADDRESSES]
|
||||
$data[self::PROPERTY_SECONDARY_ADDRESSES]
|
||||
));
|
||||
}
|
||||
if (isset($data[self::JSON_PROPERTY_AUXILIARY]) && is_array($data[self::JSON_PROPERTY_AUXILIARY])) {
|
||||
$this->setAuxiliary($data[self::JSON_PROPERTY_AUXILIARY]);
|
||||
if (isset($data[self::PROPERTY_AUXILIARY]) && is_array($data[self::PROPERTY_AUXILIARY])) {
|
||||
$this->setAuxiliary($data[self::PROPERTY_AUXILIARY]);
|
||||
}
|
||||
|
||||
return $this;
|
||||
@@ -261,23 +259,28 @@ class Service implements ServiceBaseInterface, ServiceMutableInterface, ServiceC
|
||||
|
||||
public function getPrimaryAddress(): AddressInterface
|
||||
{
|
||||
return new Address($this->primaryAddress);
|
||||
return Address::fromArray($this->primaryAddress);
|
||||
}
|
||||
|
||||
public function setPrimaryAddress(AddressInterface $value): static
|
||||
{
|
||||
$this->primaryAddress = $value->getAddress();
|
||||
$this->primaryAddress = $value->toArray();
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getSecondaryAddresses(): array
|
||||
{
|
||||
return $this->secondaryAddresses;
|
||||
return array_map(
|
||||
fn($addr) => $addr instanceof AddressInterface ? $addr : Address::fromArray(is_array($addr) ? $addr : ['address' => (string) $addr])
|
||||
, $this->secondaryAddresses);
|
||||
}
|
||||
|
||||
public function setSecondaryAddresses(array $addresses): static
|
||||
{
|
||||
$this->secondaryAddresses = $addresses;
|
||||
$this->secondaryAddresses = array_map(
|
||||
fn($addr) => $addr instanceof AddressInterface ? $addr : Address::fromArray(is_array($addr) ? $addr : ['address' => (string) $addr]),
|
||||
$addresses
|
||||
);
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -285,7 +288,7 @@ class Service implements ServiceBaseInterface, ServiceMutableInterface, ServiceC
|
||||
{
|
||||
$address = strtolower(trim($address));
|
||||
|
||||
if ($this->primaryAddress && strtolower($this->primaryAddress) === $address) {
|
||||
if ($this->primaryAddress && strtolower($this->primaryAddress['address'] ?? '') === $address) {
|
||||
return true;
|
||||
}
|
||||
foreach ($this->secondaryAddresses as $secondary) {
|
||||
@@ -580,7 +583,7 @@ class Service implements ServiceBaseInterface, ServiceMutableInterface, ServiceC
|
||||
|
||||
foreach ($identifiers as $collection => $entities) {
|
||||
$uids = array_keys($entities);
|
||||
foreach ($this->remoteService->entityFetch((string) $collection, ...$uids) as $uid => $message) {
|
||||
foreach ($this->remoteService->entityFetch((string) $collection, null, ...$uids) as $uid => $message) {
|
||||
$resource = $this->entityFresh();
|
||||
$resource->fromImap($message, $collection);
|
||||
yield $resource->urn() => $resource;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { ServiceObject } from '@KTXM/MailManager/models/service'
|
||||
import { ServiceAddressObject } from '@KTXM/MailManager/models/address'
|
||||
|
||||
type AuxiliaryTab = 'addresses' | 'messages' | 'sync'
|
||||
type DeleteMode = 'soft' | 'hard'
|
||||
@@ -16,8 +17,8 @@ const emit = defineEmits<{
|
||||
const activeTab = ref<AuxiliaryTab>('addresses')
|
||||
const deleteMode = ref<DeleteMode>('soft')
|
||||
const deleteDestination = ref('Trash')
|
||||
const primaryAddress = ref('')
|
||||
const secondaryAddresses = ref('')
|
||||
const primaryAddress = ref<ServiceAddressObject>(new ServiceAddressObject())
|
||||
const secondaryAddresses = ref<ServiceAddressObject[]>([])
|
||||
|
||||
const settingGroups = [
|
||||
{
|
||||
@@ -61,10 +62,6 @@ const destinationHint = computed(() => {
|
||||
return 'Mailbox identifier or well-known role target, for example Trash.'
|
||||
})
|
||||
|
||||
const secondaryAddressesHint = computed(() => {
|
||||
return 'Use one address per line. Commas are also accepted.'
|
||||
})
|
||||
|
||||
watch(
|
||||
() => props.service,
|
||||
service => {
|
||||
@@ -86,17 +83,17 @@ watch(
|
||||
}
|
||||
|
||||
if (sameAuxiliary(nextService.auxiliary ?? {}, nextAuxiliary)) {
|
||||
if (sameAddresses(nextService, primaryAddress.value, secondaryAddresses.value)) {
|
||||
if (sameAddresses(nextService)) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
nextService.primaryAddress = normalizePrimaryAddress(primaryAddress.value)
|
||||
nextService.secondaryAddresses = normalizeSecondaryAddresses(secondaryAddresses.value)
|
||||
nextService.primaryAddress = primaryAddress.value.empty ? null : primaryAddress.value
|
||||
nextService.secondaryAddresses = dedupeAddresses(secondaryAddresses.value)
|
||||
nextService.auxiliary = nextAuxiliary
|
||||
emit('update:service', nextService)
|
||||
},
|
||||
{ immediate: true }
|
||||
{ deep: true, immediate: true }
|
||||
)
|
||||
|
||||
function syncFromService(service?: ServiceObject) {
|
||||
@@ -105,8 +102,8 @@ function syncFromService(service?: ServiceObject) {
|
||||
deleteDestination.value = typeof auxiliary.deleteDestination === 'string' && auxiliary.deleteDestination.length > 0
|
||||
? auxiliary.deleteDestination
|
||||
: 'Trash'
|
||||
primaryAddress.value = service?.primaryAddress ?? ''
|
||||
secondaryAddresses.value = (service?.secondaryAddresses ?? []).join('\n')
|
||||
primaryAddress.value = service?.primaryAddress ?? new ServiceAddressObject()
|
||||
secondaryAddresses.value = service?.secondaryAddresses ?? []
|
||||
}
|
||||
|
||||
function normalizeDeleteDestination(value: string): string {
|
||||
@@ -114,16 +111,23 @@ function normalizeDeleteDestination(value: string): string {
|
||||
return trimmedValue.length > 0 ? trimmedValue : 'Trash'
|
||||
}
|
||||
|
||||
function normalizePrimaryAddress(value: string): string | null {
|
||||
const trimmedValue = value.trim()
|
||||
return trimmedValue.length > 0 ? trimmedValue : null
|
||||
function dedupeAddresses(entries: ServiceAddressObject[]): ServiceAddressObject[] {
|
||||
const populated = entries.filter(entry => !entry.empty)
|
||||
return populated.filter((entry, index) =>
|
||||
populated.findIndex(candidate => candidate.matches(entry.address)) === index)
|
||||
}
|
||||
|
||||
function normalizeSecondaryAddresses(value: string): string[] {
|
||||
return value
|
||||
.split(/\r?\n|,/)
|
||||
.map(entry => entry.trim())
|
||||
.filter((entry, index, entries) => entry.length > 0 && entries.indexOf(entry) === index)
|
||||
function addSecondaryAddress() {
|
||||
secondaryAddresses.value.push(new ServiceAddressObject())
|
||||
}
|
||||
|
||||
function removeSecondaryAddress(index: number) {
|
||||
secondaryAddresses.value.splice(index, 1)
|
||||
}
|
||||
|
||||
function validAddress(value: string): boolean | string {
|
||||
const trimmedValue = value.trim()
|
||||
return trimmedValue.length === 0 || /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(trimmedValue) || 'Invalid email address'
|
||||
}
|
||||
|
||||
function sameAuxiliary(current: Record<string, any>, next: Record<string, any>): boolean {
|
||||
@@ -131,9 +135,19 @@ function sameAuxiliary(current: Record<string, any>, next: Record<string, any>):
|
||||
&& (current.deleteDestination ?? undefined) === (next.deleteDestination ?? undefined)
|
||||
}
|
||||
|
||||
function sameAddresses(service: ServiceObject, nextPrimaryAddress: string, nextSecondaryAddresses: string): boolean {
|
||||
return (service.primaryAddress ?? null) === normalizePrimaryAddress(nextPrimaryAddress)
|
||||
&& JSON.stringify(service.secondaryAddresses) === JSON.stringify(normalizeSecondaryAddresses(nextSecondaryAddresses))
|
||||
function sameAddresses(service: ServiceObject): boolean {
|
||||
const nextPrimary = primaryAddress.value.empty ? null : primaryAddress.value
|
||||
const nextSecondary = dedupeAddresses(secondaryAddresses.value)
|
||||
return sameAddress(service.primaryAddress, nextPrimary)
|
||||
&& service.secondaryAddresses.length === nextSecondary.length
|
||||
&& service.secondaryAddresses.every((entry, index) => sameAddress(entry, nextSecondary[index]))
|
||||
}
|
||||
|
||||
function sameAddress(current: ServiceAddressObject | null, next: ServiceAddressObject | null): boolean {
|
||||
if (current === null || next === null) {
|
||||
return current === next
|
||||
}
|
||||
return current.equals(next)
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -165,23 +179,66 @@ function sameAddresses(service: ServiceObject, nextPrimaryAddress: string, nextS
|
||||
Configure the primary mailbox identity and any additional sender aliases exposed by this service.
|
||||
</p>
|
||||
|
||||
<v-text-field
|
||||
v-model="primaryAddress"
|
||||
label="Primary Address"
|
||||
variant="outlined"
|
||||
prepend-inner-icon="mdi-email-outline"
|
||||
class="mb-4"
|
||||
/>
|
||||
<div class="text-subtitle-2 mb-2">Primary Address</div>
|
||||
<div class="d-flex ga-2 mb-6">
|
||||
<v-text-field
|
||||
v-model="primaryAddress.label"
|
||||
label="Display Name"
|
||||
variant="outlined"
|
||||
density="compact"
|
||||
hide-details="auto"
|
||||
/>
|
||||
<v-text-field
|
||||
v-model="primaryAddress.address"
|
||||
label="Email Address"
|
||||
variant="outlined"
|
||||
density="compact"
|
||||
prepend-inner-icon="mdi-email-outline"
|
||||
:rules="[validAddress]"
|
||||
hide-details="auto"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<v-textarea
|
||||
v-model="secondaryAddresses"
|
||||
label="Secondary Addresses"
|
||||
variant="outlined"
|
||||
prepend-inner-icon="mdi-email-multiple-outline"
|
||||
rows="4"
|
||||
:hint="secondaryAddressesHint"
|
||||
persistent-hint
|
||||
/>
|
||||
<div class="text-subtitle-2 mb-2">Secondary Addresses</div>
|
||||
<div
|
||||
v-for="(entry, index) in secondaryAddresses"
|
||||
:key="index"
|
||||
class="d-flex ga-2 mb-2"
|
||||
>
|
||||
<v-text-field
|
||||
v-model="entry.label"
|
||||
label="Display Name"
|
||||
variant="outlined"
|
||||
density="compact"
|
||||
hide-details="auto"
|
||||
/>
|
||||
<v-text-field
|
||||
v-model="entry.address"
|
||||
label="Email Address"
|
||||
variant="outlined"
|
||||
density="compact"
|
||||
prepend-inner-icon="mdi-email-multiple-outline"
|
||||
:rules="[validAddress]"
|
||||
hide-details="auto"
|
||||
/>
|
||||
<v-btn
|
||||
icon
|
||||
size="small"
|
||||
variant="text"
|
||||
@click="removeSecondaryAddress(index)"
|
||||
>
|
||||
<v-icon>mdi-delete-outline</v-icon>
|
||||
<v-tooltip activator="parent" location="bottom">Remove Alias</v-tooltip>
|
||||
</v-btn>
|
||||
</div>
|
||||
|
||||
<v-btn
|
||||
variant="tonal"
|
||||
prepend-icon="mdi-plus"
|
||||
@click="addSecondaryAddress"
|
||||
>
|
||||
Add Alias
|
||||
</v-btn>
|
||||
</div>
|
||||
</v-window-item>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user