add a way to copy a list of contacts with a shorter URL

This commit is contained in:
2026-03-02 21:14:02 -07:00
parent 4f89869a87
commit 41149ad28a

View File

@@ -197,16 +197,30 @@
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-if="contactsImporting.length > 0">
<p>
All those contacts are already in your list with the same information.
</p>
<div class="mt-3 flex flex-col items-center gap-2">
<button
data-testId="copyUnsignedImportLinkButton"
class="bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-sm text-white px-2 py-1.5 rounded w-fit"
@click="copyUnsignedImportLink"
>
Copy Unsigned Link for These Contacts
</button>
<button
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 px-2 py-1.5 rounded w-fit"
:class="{
'opacity-50 cursor-not-allowed': !canApplyLabelsToExisting,
}"
@click="handleApplyLabelsToExistingClick"
>
Apply Labels to Existing Contacts
</button>
</div>
</div>
<div v-else>
There are no contacts in that import. If some were sent, try again to
get the full text and paste it. (Note that iOS cuts off data in text
@@ -291,7 +305,8 @@ import { RouteLocationNormalizedLoaded, Router } from "vue-router";
import QuickNav from "../components/QuickNav.vue";
import EntityIcon from "../components/EntityIcon.vue";
import OfferDialog from "../components/OfferDialog.vue";
import { AppString, NotificationIface } from "../constants/app";
import { APP_SERVER, AppString, NotificationIface } from "../constants/app";
import { copyToClipboard } from "../services/ClipboardService";
import {
Contact,
ContactWithLabels,
@@ -647,6 +662,52 @@ export default class ContactImportView extends Vue {
this.checkingImports = false;
}
private buildUnsignedImportLink(): string {
const contactsForLink: Array<Contact> = this.contactsImporting.map((c) => {
const contact: Contact = {
did: c.did,
};
if (c.name) {
contact.name = c.name;
}
if (c.nextPubKeyHashB64) {
contact.nextPubKeyHashB64 = c.nextPubKeyHashB64;
}
if (c.profileImageUrl) {
contact.profileImageUrl = c.profileImageUrl;
}
if (c.publicKeyBase64) {
contact.publicKeyBase64 = c.publicKeyBase64;
}
if (typeof c.registered === "boolean") {
contact.registered = c.registered;
}
return contact;
});
const contactsParam = encodeURIComponent(JSON.stringify(contactsForLink));
return `${APP_SERVER}/deep-link/contact-import?contacts=${contactsParam}`;
}
async copyUnsignedImportLink() {
if (this.contactsImporting.length === 0) {
this.notify.error(
"No contacts are loaded to build a link.",
TIMEOUTS.SHORT,
);
return;
}
try {
const link = this.buildUnsignedImportLink();
await copyToClipboard(link);
this.notify.copied("unsigned contact import link", TIMEOUTS.STANDARD);
} catch (error) {
const fullError =
"Error copying unsigned import link: " + errorStringForLog(error);
this.$logAndConsole(fullError, true);
this.notify.error("Failed to copy link to clipboard.", TIMEOUTS.STANDARD);
}
}
/**
* Adds a new label to the selected labels list
*/
@@ -803,5 +864,20 @@ export default class ContactImportView extends Vue {
);
this.$router.push({ name: "contacts" });
}
private get canApplyLabelsToExisting(): boolean {
return this.applyLabelsToExisting && this.selectedLabels.length > 0;
}
private async handleApplyLabelsToExistingClick() {
if (!this.canApplyLabelsToExisting) {
this.notify.warning(
`You must choose some labels and check the "Apply" checkbox to use this.`,
TIMEOUTS.LONG,
);
return;
}
await this.importContacts();
}
}
</script>