allow application of labels to contacts that are imported

This commit is contained in:
2026-01-17 16:28:33 -07:00
parent 08f91e4c96
commit 46f2cbfcc6

View File

@@ -36,7 +36,97 @@
Make my activity visible to new contacts.
</span>
<div v-if="sameCount > 0">
<!-- Labels Section -->
<div
v-if="contactsImporting"
class="mt-4 p-4 border-2 border-slate-700 rounded"
>
<label class="block text-sm font-medium text-gray-700 mb-2">
Labels for Imported Contacts
</label>
<!-- Apply to existing contacts checkbox -->
<div v-if="sameCount > 0" class="mb-3 flex items-center">
<input
id="applyLabelsToExisting"
v-model="applyLabelsToExisting"
type="checkbox"
class="mr-2"
/>
<label
for="applyLabelsToExisting"
class="text-sm text-gray-700 cursor-pointer"
>
Apply these labels to existing contacts (contacts that are already
in your list)
</label>
</div>
<!-- Selected Labels -->
<div class="flex flex-wrap gap-2 mb-3">
<span
v-for="label in selectedLabels"
:key="label"
class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800"
>
{{ label }}
<button
type="button"
class="flex-shrink-0 ml-1.5 inline-flex text-blue-400 hover:text-blue-600 focus:outline-none"
@click="removeSelectedLabel(label)"
>
<font-awesome icon="xmark" class="h-4 w-4" />
</button>
</span>
<span
v-if="selectedLabels.length === 0"
class="text-xs text-gray-400 italic"
>
No labels selected
</span>
</div>
<!-- Label Input -->
<div class="flex gap-2 mb-3">
<input
v-model="newLabelInput"
type="text"
placeholder="Add or create a label..."
class="block w-full border border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 text-sm p-2"
@keyup.enter="addNewLabel"
/>
<button
class="px-4 py-1 bg-green-500 text-white rounded-md text-sm whitespace-nowrap"
@click="addNewLabel"
>
Add Label
</button>
</div>
<!-- Available Labels -->
<div v-if="availableLabels.length > 0" class="mt-3">
<p class="text-xs font-medium text-gray-700 mb-2">
Available labels (click to toggle):
</p>
<div class="flex flex-wrap gap-2">
<button
v-for="label in availableLabels"
:key="label"
class="inline-flex items-center px-3 py-1 rounded text-sm font-medium transition-colors"
:class="
selectedLabels.includes(label)
? 'bg-blue-500 text-white hover:bg-blue-600'
: 'bg-gray-100 text-gray-800 hover:bg-gray-200'
"
@click="toggleLabel(label)"
>
{{ label }}
</button>
</div>
</div>
</div>
<div v-if="sameCount > 0" class="mt-2">
<span v-if="sameCount == 1"
>One contact is the same as an existing contact</span
>
@@ -48,7 +138,7 @@
<!-- Results List -->
<ul
v-if="contactsImporting.length > sameCount"
class="border-t border-slate-300"
class="mt-2 border-t border-slate-300"
>
<li v-for="(contact, index) in contactsImporting" :key="contact.did">
<div
@@ -104,11 +194,18 @@
class="bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-sm text-white mt-2 px-2 py-1.5 rounded"
@click="importContacts"
>
Import Selected Contacts
Import Contacts
</button>
</ul>
<p v-else-if="contactsImporting.length > 0">
All those contacts are already in your list with the same information.
<button
v-if="applyLabelsToExisting"
class="bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-sm text-white mt-2 px-2 py-1.5 rounded"
@click="importContacts"
>
Apply Labels to Existing Contacts
</button>
</p>
<div v-else>
There are no contacts in that import. If some were sent, try again to
@@ -312,6 +409,17 @@ export default class ContactImportView extends Vue {
/** Count of duplicate contacts found */
sameCount = 0;
// --- Labels ---
/** Labels selected to be applied to imported contacts */
selectedLabels: Array<string> = [];
/** Input for new label creation */
newLabelInput = "";
/** All available labels from existing contacts */
availableLabels: Array<string> = [];
/** Whether to apply labels to existing contacts as well */
applyLabelsToExisting = false;
/**
* Component lifecycle hook that initializes the contact import process
*
@@ -338,6 +446,7 @@ export default class ContactImportView extends Vue {
this.notify = createNotifyHelpers(this.$notify);
await this.initializeSettings();
await this.loadAvailableLabels();
await this.processQueryParams();
await this.processJwtFromPath();
await this.handleAutoImport();
@@ -357,6 +466,21 @@ export default class ContactImportView extends Vue {
this.apiServer = settings.apiServer || "";
}
/**
* Loads all available labels from existing contacts
*/
private async loadAvailableLabels() {
try {
this.availableLabels = await this.$getUniqueContactLabels();
} catch (error) {
const fullError =
"Error loading available labels: " + errorStringForLog(error);
this.$logAndConsole(fullError, true);
// Don't show error to user as this is non-critical
this.availableLabels = [];
}
}
/**
* Processes contacts from URL query parameters
*/
@@ -511,6 +635,46 @@ export default class ContactImportView extends Vue {
this.checkingImports = false;
}
/**
* Adds a new label to the selected labels list
*/
addNewLabel() {
const label = this.newLabelInput.trim();
if (label && !this.selectedLabels.includes(label)) {
this.selectedLabels.push(label);
// Add to available labels if it's new
if (!this.availableLabels.includes(label)) {
this.availableLabels.push(label);
this.availableLabels.sort();
}
this.newLabelInput = "";
}
}
/**
* Removes a label from the selected labels list
* @param label Label to remove
*/
removeSelectedLabel(label: string) {
const index = this.selectedLabels.indexOf(label);
if (index > -1) {
this.selectedLabels.splice(index, 1);
}
}
/**
* Toggles a label in the selected labels list
* @param label Label to toggle
*/
toggleLabel(label: string) {
const index = this.selectedLabels.indexOf(label);
if (index > -1) {
this.selectedLabels.splice(index, 1);
} else {
this.selectedLabels.push(label);
}
}
/**
* Imports selected contacts and sets visibility if requested
* Updates existing contacts or adds new ones
@@ -520,7 +684,7 @@ export default class ContactImportView extends Vue {
let importedCount = 0,
updatedCount = 0;
// Process selected contacts
// Process selected contacts (new contacts or existing with changes)
for (let i = 0; i < this.contactsImporting.length; i++) {
if (this.contactsSelected[i]) {
const contactWithLabels = this.contactsImporting[i];
@@ -528,25 +692,61 @@ export default class ContactImportView extends Vue {
...contactWithLabels,
labels: undefined,
};
const contactLabels = contactWithLabels.labels || [];
// Merge existing labels with selected labels for imported contacts
const existingLabels = contactWithLabels.labels || [];
const mergedLabels = Array.from(
new Set([...existingLabels, ...this.selectedLabels]),
);
const existingContact = this.contactsExisting[contact.did];
if (existingContact) {
// Update existing contact
await this.$updateContact(contact.did, contact);
// update the labels for the contact
await this.$updateContactLabels(contact.did, contactLabels);
await this.$updateContactLabels(contact.did, mergedLabels);
updatedCount++;
} else {
// Add new contact
await this.$insertContact(contact);
// add the labels for the contact
await this.$insertContactLabels(contact.did, contactLabels);
await this.$insertContactLabels(contact.did, mergedLabels);
importedCount++;
}
}
}
let labelsAppliedToExistingCount = 0;
// Apply selected labels to existing contacts with no changes if checkbox is checked
if (this.applyLabelsToExisting && this.selectedLabels.length > 0) {
for (let i = 0; i < this.contactsImporting.length; i++) {
// Skip if this contact was already processed (selected)
if (!this.contactsSelected[i]) {
const contactWithLabels = this.contactsImporting[i];
const existingContact = this.contactsExisting[contactWithLabels.did];
// Only process existing contacts (not selected means no changes)
if (existingContact) {
// Get current labels for this contact
const currentLabels = await this.$getContactLabelsForDid(
contactWithLabels.did,
);
// Merge current labels with selected labels
const mergedLabels = Array.from(
new Set([...currentLabels, ...this.selectedLabels]),
);
// Update labels if there are new ones to add
if (mergedLabels.length > currentLabels.length) {
await this.$updateContactLabels(
contactWithLabels.did,
mergedLabels,
);
labelsAppliedToExistingCount++;
}
}
}
}
}
// Set visibility if requested
if (this.makeVisible) {
const failedVisibileToContacts = [];
@@ -583,7 +783,10 @@ export default class ContactImportView extends Vue {
// Show success notification
this.notify.success(
`${importedCount} contact${importedCount == 1 ? "" : "s"} imported.` +
(updatedCount ? ` ${updatedCount} updated.` : ""),
(updatedCount ? ` ${updatedCount} updated.` : "") +
(labelsAppliedToExistingCount
? ` ${labelsAppliedToExistingCount} labels applied to existing contacts.`
: ""),
TIMEOUTS.STANDARD,
);
this.$router.push({ name: "contacts" });