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 Import Contacts
</button> </button>
</ul> </ul>
<p v-else-if="contactsImporting.length > 0"> <div v-else-if="contactsImporting.length > 0">
<p>
All those contacts are already in your list with the same information. 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 <button
v-if="applyLabelsToExisting" data-testId="copyUnsignedImportLinkButton"
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" 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="importContacts" @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 Apply Labels to Existing Contacts
</button> </button>
</p> </div>
</div>
<div v-else> <div v-else>
There are no contacts in that import. If some were sent, try again to 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 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 QuickNav from "../components/QuickNav.vue";
import EntityIcon from "../components/EntityIcon.vue"; import EntityIcon from "../components/EntityIcon.vue";
import OfferDialog from "../components/OfferDialog.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 { import {
Contact, Contact,
ContactWithLabels, ContactWithLabels,
@@ -647,6 +662,52 @@ export default class ContactImportView extends Vue {
this.checkingImports = false; 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 * Adds a new label to the selected labels list
*/ */
@@ -803,5 +864,20 @@ export default class ContactImportView extends Vue {
); );
this.$router.push({ name: "contacts" }); 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> </script>