atom/ics link copy to clipboard: using in "GroupView" + "EditIdentity" + "NotificationsView" + "AboutInstanceView" - refactoring to use same mecanizem (Issue #1496)

This commit is contained in:
Laurent GAY
2025-07-30 17:29:19 +02:00
committed by setop
parent b339de8815
commit 35b73eb20c
5 changed files with 141 additions and 59 deletions

View File

@@ -1,3 +1,5 @@
import { reactive } from "vue";
export const twitterShareUrl = ( export const twitterShareUrl = (
url: string | undefined, url: string | undefined,
text: string | undefined text: string | undefined
@@ -75,6 +77,32 @@ export const mastodonShareUrl = (
)}`; )}`;
}; };
export const showCopiedTooltip = reactive({ ics: false, atom: false });
export const initCopiedTooltipShow = (): void => {
showCopiedTooltip.ics = false;
showCopiedTooltip.atom = false;
};
export const tokenToURL = (subURL: string): string => {
return `${window.location.origin}/${subURL}`;
};
export const copyURL = (
e: Event,
url: string,
format: "ics" | "atom"
): void => {
if (navigator.clipboard) {
e.preventDefault();
navigator.clipboard.writeText(url);
showCopiedTooltip[format] = true;
setTimeout(() => {
showCopiedTooltip[format] = false;
}, 2000);
}
};
const basicTextToEncode = (url: string, text: string): string => { const basicTextToEncode = (url: string, text: string): string => {
return `${text}\r\n${url}`; return `${text}\r\n${url}`;
}; };

View File

@@ -86,20 +86,36 @@
<tr class="bg-white border-b dark:bg-gray-800 dark:border-gray-700"> <tr class="bg-white border-b dark:bg-gray-800 dark:border-gray-700">
<td>{{ t("Instance feeds") }}</td> <td>{{ t("Instance feeds") }}</td>
<td v-if="config.instanceFeeds.enabled" class="flex gap-2"> <td v-if="config.instanceFeeds.enabled" class="flex gap-2">
<o-tooltip
:label="t('URL copied to clipboard')"
:active="showCopiedTooltip.atom"
variant="success"
position="left"
/>
<o-button <o-button
tag="a" tag="a"
size="small"
icon-left="rss" icon-left="rss"
href="/feed/instance/atom" @click="
(e: Event) =>
copyURL(e, tokenToURL('feed/instance/atom'), 'atom')
"
:href="tokenToURL('feed/instance/atom')"
target="_blank" target="_blank"
>{{ t("RSS/Atom Feed") }}</o-button >{{ t("RSS/Atom Feed") }}</o-button
> >
<o-tooltip
:label="t('URL copied to clipboard')"
:active="showCopiedTooltip.ics"
variant="success"
position="left"
/>
<o-button <o-button
tag="a" tag="a"
size="small" @click="
(e: Event) => copyURL(e, tokenToURL('feed/instance/ics'), 'ics')
"
icon-left="calendar-sync" icon-left="calendar-sync"
href="/feed/instance/ics" :href="tokenToURL('feed/instance/ics')"
target="_blank" target="_blank"
>{{ t("ICS/WebCal Feed") }}</o-button >{{ t("ICS/WebCal Feed") }}</o-button
> >
@@ -124,6 +140,14 @@ import { useQuery } from "@vue/apollo-composable";
import { computed } from "vue"; import { computed } from "vue";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import { useHead } from "@/utils/head"; import { useHead } from "@/utils/head";
import {
showCopiedTooltip,
initCopiedTooltipShow,
copyURL,
tokenToURL,
} from "@/utils/share";
initCopiedTooltipShow();
const { result: configResult } = useQuery<{ config: IConfig }>(ABOUT); const { result: configResult } = useQuery<{ config: IConfig }>(ABOUT);

View File

@@ -133,9 +133,13 @@
icon-left="rss" icon-left="rss"
@click=" @click="
(e: Event) => (e: Event) =>
copyURL(e, tokenToURL(feedToken.token, 'atom'), 'atom') copyURL(
e,
tokenToURL('events/going/' + feedToken.token + '/atom'),
'atom'
)
" "
:href="tokenToURL(feedToken.token, 'atom')" :href="tokenToURL('events/going/' + feedToken.token + '/atom')"
target="_blank" target="_blank"
>{{ t("RSS/Atom Feed") }}</o-button >{{ t("RSS/Atom Feed") }}</o-button
> >
@@ -149,10 +153,14 @@
tag="a" tag="a"
@click=" @click="
(e: Event) => (e: Event) =>
copyURL(e, tokenToURL(feedToken.token, 'ics'), 'ics') copyURL(
e,
tokenToURL('events/going/' + feedToken.token + '/ics'),
'ics'
)
" "
icon-left="calendar-sync" icon-left="calendar-sync"
:href="tokenToURL(feedToken.token, 'ics')" :href="tokenToURL('events/going/' + feedToken.token + '/ics')"
target="_blank" target="_blank"
>{{ t("ICS/WebCal Feed") }}</o-button >{{ t("ICS/WebCal Feed") }}</o-button
> >
@@ -225,7 +233,7 @@ import {
} from "@/composition/apollo/actor"; } from "@/composition/apollo/actor";
import { useMutation, useQuery, useApolloClient } from "@vue/apollo-composable"; import { useMutation, useQuery, useApolloClient } from "@vue/apollo-composable";
import { useAvatarMaxSize } from "@/composition/config"; import { useAvatarMaxSize } from "@/composition/config";
import { computed, inject, reactive, ref, watch } from "vue"; import { computed, inject, ref, watch } from "vue";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import { convertToUsername } from "@/utils/username"; import { convertToUsername } from "@/utils/username";
import { Dialog } from "@/plugins/dialog"; import { Dialog } from "@/plugins/dialog";
@@ -233,6 +241,14 @@ import { Notifier } from "@/plugins/notifier";
import { AbsintheGraphQLErrors } from "@/types/errors.model"; import { AbsintheGraphQLErrors } from "@/types/errors.model";
import { ICurrentUser } from "@/types/current-user.model"; import { ICurrentUser } from "@/types/current-user.model";
import { useHead } from "@/utils/head"; import { useHead } from "@/utils/head";
import {
showCopiedTooltip,
initCopiedTooltipShow,
copyURL,
tokenToURL,
} from "@/utils/share";
initCopiedTooltipShow();
const { t } = useI18n({ useScope: "global" }); const { t } = useI18n({ useScope: "global" });
const router = useRouter(); const router = useRouter();
@@ -301,7 +317,6 @@ const avatarMaxSize = useAvatarMaxSize();
const errors = ref<string[]>([]); const errors = ref<string[]>([]);
const avatarFile = ref<File | null>(null); const avatarFile = ref<File | null>(null);
const showCopiedTooltip = reactive({ ics: false, atom: false });
const isUpdate = computed(() => props.isUpdate); const isUpdate = computed(() => props.isUpdate);
const identityName = computed(() => props.identityName); const identityName = computed(() => props.identityName);
@@ -518,21 +533,6 @@ const getInstanceHost = computed((): string => {
return MOBILIZON_INSTANCE_HOST; return MOBILIZON_INSTANCE_HOST;
}); });
const tokenToURL = (token: string, format: string): string => {
return `${window.location.origin}/events/going/${token}/${format}`;
};
const copyURL = (e: Event, url: string, format: "ics" | "atom"): void => {
if (navigator.clipboard) {
e.preventDefault();
navigator.clipboard.writeText(url);
showCopiedTooltip[format] = true;
setTimeout(() => {
showCopiedTooltip[format] = false;
}, 2000);
}
};
const generateFeedTokens = async (): Promise<void> => { const generateFeedTokens = async (): Promise<void> => {
await createNewFeedToken({ actor_id: identity.value?.id }); await createNewFeedToken({ actor_id: identity.value?.id });
}; };

View File

@@ -244,6 +244,12 @@
> >
{{ t("Share") }} {{ t("Share") }}
</o-button> </o-button>
<o-tooltip
:label="t('URL copied to clipboard')"
:active="showCopiedTooltip.atom || showCopiedTooltip.ics"
variant="success"
position="right"
/>
<o-dropdown aria-role="list"> <o-dropdown aria-role="list">
<template #trigger> <template #trigger>
<o-button <o-button
@@ -278,8 +284,16 @@
/> />
<o-dropdown-item has-link aria-role="menuitem"> <o-dropdown-item has-link aria-role="menuitem">
<a <a
:href="`@${preferredUsername}/feed/atom`" :href="tokenToURL('@' + preferredUsername + '/feed/atom')"
:title="t('Atom feed for events and posts')" :title="t('Atom feed for events and posts')"
@click="
(e: Event) =>
copyURL(
e,
tokenToURL('@' + preferredUsername + '/feed/atom'),
'atom'
)
"
class="inline-flex gap-1" class="inline-flex gap-1"
> >
<RSS /> <RSS />
@@ -288,8 +302,16 @@
</o-dropdown-item> </o-dropdown-item>
<o-dropdown-item has-link aria-role="menuitem"> <o-dropdown-item has-link aria-role="menuitem">
<a <a
:href="`@${preferredUsername}/feed/ics`" :href="tokenToURL('@' + preferredUsername + '/feed/ics')"
:title="t('ICS feed for events')" :title="t('ICS feed for events')"
@click="
(e: Event) =>
copyURL(
e,
tokenToURL('@' + preferredUsername + '/feed/ics'),
'ics'
)
"
class="inline-flex gap-1" class="inline-flex gap-1"
> >
<CalendarSync /> <CalendarSync />
@@ -674,6 +696,14 @@ import { useGroupResourcesList } from "@/composition/apollo/resources";
import { useGroupMembers } from "@/composition/apollo/members"; import { useGroupMembers } from "@/composition/apollo/members";
import GroupSection from "@/components/Group/GroupSection.vue"; import GroupSection from "@/components/Group/GroupSection.vue";
import { useIsLongEvents } from "@/composition/apollo/config"; import { useIsLongEvents } from "@/composition/apollo/config";
import {
showCopiedTooltip,
initCopiedTooltipShow,
copyURL,
tokenToURL,
} from "@/utils/share";
initCopiedTooltipShow();
const props = defineProps<{ const props = defineProps<{
preferredUsername: string; preferredUsername: string;

View File

@@ -248,13 +248,21 @@
icon-left="rss" icon-left="rss"
@click=" @click="
(e: Event) => (e: Event) =>
copyURL(e, tokenToURL(feedToken.token, 'atom'), 'atom') copyURL(
e,
tokenToURL('events/going/' + feedToken.token + 'atom'),
'atom'
)
" "
@keyup.enter=" @keyup.enter="
(e: Event) => (e: Event) =>
copyURL(e, tokenToURL(feedToken.token, 'atom'), 'atom') copyURL(
e,
tokenToURL('events/going/' + feedToken.token + 'atom'),
'atom'
)
" "
:href="tokenToURL(feedToken.token, 'atom')" :href="tokenToURL('events/going/' + feedToken.token + 'atom')"
target="_blank" target="_blank"
>{{ $t("RSS/Atom Feed") }}</o-button >{{ $t("RSS/Atom Feed") }}</o-button
> >
@@ -269,14 +277,22 @@
tag="a" tag="a"
@click=" @click="
(e: Event) => (e: Event) =>
copyURL(e, tokenToURL(feedToken.token, 'ics'), 'ics') copyURL(
e,
tokenToURL('events/going/' + feedToken.token + 'ics'),
'ics'
)
" "
@keyup.enter=" @keyup.enter="
(e: Event) => (e: Event) =>
copyURL(e, tokenToURL(feedToken.token, 'ics'), 'ics') copyURL(
e,
tokenToURL('events/going/' + feedToken.token + 'ics'),
'ics'
)
" "
icon-left="calendar-sync" icon-left="calendar-sync"
:href="tokenToURL(feedToken.token, 'ics')" :href="tokenToURL('events/going/' + feedToken.token + 'ics')"
target="_blank" target="_blank"
>{{ $t("ICS/WebCal Feed") }}</o-button >{{ $t("ICS/WebCal Feed") }}</o-button
> >
@@ -328,19 +344,19 @@ import {
import merge from "lodash/merge"; import merge from "lodash/merge";
import { CONFIG } from "@/graphql/config"; import { CONFIG } from "@/graphql/config";
import { useMutation, useQuery } from "@vue/apollo-composable"; import { useMutation, useQuery } from "@vue/apollo-composable";
import { import { computed, inject, onBeforeMount, onMounted, ref, watch } from "vue";
computed,
inject,
onBeforeMount,
onMounted,
reactive,
ref,
watch,
} from "vue";
import { IConfig } from "@/types/config.model"; import { IConfig } from "@/types/config.model";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import { useHead } from "@/utils/head"; import { useHead } from "@/utils/head";
import { Dialog } from "@/plugins/dialog"; import { Dialog } from "@/plugins/dialog";
import {
showCopiedTooltip,
initCopiedTooltipShow,
copyURL,
tokenToURL,
} from "@/utils/share";
initCopiedTooltipShow();
type NotificationSubType = { label: string; id: string }; type NotificationSubType = { label: string; id: string };
type NotificationType = { label: string; subtypes: NotificationSubType[] }; type NotificationType = { label: string; subtypes: NotificationSubType[] };
@@ -380,7 +396,6 @@ const groupNotifications = ref<INotificationPendingEnum | undefined>(
); );
const notificationPendingParticipationValues = ref<Record<string, unknown>>({}); const notificationPendingParticipationValues = ref<Record<string, unknown>>({});
const groupNotificationsValues = ref<Record<string, unknown>>({}); const groupNotificationsValues = ref<Record<string, unknown>>({});
const showCopiedTooltip = reactive({ ics: false, atom: false });
const subscribed = ref(false); const subscribed = ref(false);
const canShowWebPush = ref(false); const canShowWebPush = ref(false);
@@ -635,21 +650,6 @@ const { mutate: updateSetting } = useMutation<{ setUserSettings: string }>(
() => ({ refetchQueries: [{ query: USER_NOTIFICATIONS }] }) () => ({ refetchQueries: [{ query: USER_NOTIFICATIONS }] })
); );
const tokenToURL = (token: string, format: string): string => {
return `${window.location.origin}/events/going/${token}/${format}`;
};
const copyURL = (e: Event, url: string, format: "ics" | "atom"): void => {
if (navigator.clipboard) {
e.preventDefault();
navigator.clipboard.writeText(url);
showCopiedTooltip[format] = true;
setTimeout(() => {
showCopiedTooltip[format] = false;
}, 2000);
}
};
const dialog = inject<Dialog>("dialog"); const dialog = inject<Dialog>("dialog");
const openRegenerateFeedTokensConfirmation = () => { const openRegenerateFeedTokensConfirmation = () => {