Added copy link component
This commit is contained in:
@@ -103,14 +103,14 @@
|
|||||||
text-underline-offset: 2px;
|
text-underline-offset: 2px;
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
|
|
||||||
&:hover {
|
&:hover,
|
||||||
|
&:focus-visible {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: var(--green-500);
|
color: var(--green-500);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.map-link {
|
.map-link {
|
||||||
margin-top: .5rem;
|
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: .5ch;
|
gap: .5ch;
|
||||||
@@ -123,7 +123,13 @@
|
|||||||
|
|
||||||
.tooltip-content > * + * {
|
.tooltip-content > * + * {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
margin-top: .4em;
|
margin-top: .66em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip-footer {
|
||||||
|
display: grid;
|
||||||
|
gap: .15em;
|
||||||
|
font-size: .95em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
81
src/components/global/CopyText.astro
Normal file
81
src/components/global/CopyText.astro
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
---
|
||||||
|
import { t } from '@/i18n/store';
|
||||||
|
import { getLangFromUrl } from '@/i18n/utils';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
text: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
const { text } = Astro.props
|
||||||
|
|
||||||
|
const lang = getLangFromUrl(Astro.url);
|
||||||
|
const placeholder = t('common.copyLink', lang)
|
||||||
|
const message = t('common.copiedLink', lang)
|
||||||
|
---
|
||||||
|
|
||||||
|
<button data-target={text} data-copy={placeholder} data-copy-message={message}>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><path d="M240,88.23a54.43,54.43,0,0,1-16,37L189.25,160a54.27,54.27,0,0,1-38.63,16h-.05A54.63,54.63,0,0,1,96,119.84a8,8,0,0,1,16,.45A38.62,38.62,0,0,0,150.58,160h0a38.39,38.39,0,0,0,27.31-11.31l34.75-34.75a38.63,38.63,0,0,0-54.63-54.63l-11,11A8,8,0,0,1,135.7,59l11-11A54.65,54.65,0,0,1,224,48,54.86,54.86,0,0,1,240,88.23ZM109,185.66l-11,11A38.41,38.41,0,0,1,70.6,208h0a38.63,38.63,0,0,1-27.29-65.94L78,107.31A38.63,38.63,0,0,1,144,135.71a8,8,0,0,0,16,.45A54.86,54.86,0,0,0,144,96a54.65,54.65,0,0,0-77.27,0L32,130.75A54.62,54.62,0,0,0,70.56,224h0a54.28,54.28,0,0,0,38.64-16l11-11A8,8,0,0,0,109,185.66Z"></path></svg>
|
||||||
|
|
||||||
|
<span data-copy-text>
|
||||||
|
{placeholder}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const copyColor = 'initial'
|
||||||
|
const copyColorSuccess = 'var(--green-500)'
|
||||||
|
const copyTimeout = 2000
|
||||||
|
|
||||||
|
const dataCopyButtons = document.querySelectorAll<HTMLButtonElement>('button[data-copy]')
|
||||||
|
|
||||||
|
dataCopyButtons.forEach(button => {
|
||||||
|
button.addEventListener('click', () => {
|
||||||
|
const copyText = button.getAttribute('data-target')
|
||||||
|
navigator.clipboard.writeText(copyText!)
|
||||||
|
changeCopyText(button)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
function changeCopyText(copyButton: HTMLButtonElement) {
|
||||||
|
const copyPlaceholder = copyButton.getAttribute('data-copy')
|
||||||
|
const copyPlaceholderSuccess = copyButton.getAttribute('data-copy-message')
|
||||||
|
const dataText = copyButton.querySelector<HTMLSpanElement>('[data-copy-text]')
|
||||||
|
const svg = copyButton.querySelector<SVGElement>('svg')
|
||||||
|
|
||||||
|
if (!dataText || !svg) return
|
||||||
|
|
||||||
|
dataText.textContent = copyPlaceholderSuccess
|
||||||
|
|
||||||
|
dataText.style.color = copyColorSuccess
|
||||||
|
svg.style.fill = copyColorSuccess
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
dataText.textContent = copyPlaceholder
|
||||||
|
dataText.style.color = copyColor
|
||||||
|
svg.style.fill = copyColor
|
||||||
|
}, copyTimeout)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
button {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: .5ch;
|
||||||
|
color: var(--slate-700);
|
||||||
|
cursor: pointer;
|
||||||
|
text-underline-offset: 2px;
|
||||||
|
text-decoration: none;
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:focus-visible {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
svg {
|
||||||
|
width: 1rem;
|
||||||
|
height: 1rem;
|
||||||
|
fill: var(--slate-700);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -4,6 +4,8 @@ import type { MapProps } from '@/types/Map';
|
|||||||
import MarkerCreator from './overlay/MarkerCreator.vue';
|
import MarkerCreator from './overlay/MarkerCreator.vue';
|
||||||
import { t } from '@/i18n/store';
|
import { t } from '@/i18n/store';
|
||||||
import { getLangFromUrl } from '@/i18n/utils';
|
import { getLangFromUrl } from '@/i18n/utils';
|
||||||
|
import { normalizeString } from '@/utils/Strings';
|
||||||
|
import CopyText from '../global/CopyText.astro';
|
||||||
|
|
||||||
interface Props extends MapProps {}
|
interface Props extends MapProps {}
|
||||||
|
|
||||||
@@ -33,32 +35,44 @@ const seeMapText = t('maps.go-to-map', lang)
|
|||||||
<MarkerCreator client:only="vue" mapKey={mapKey} />
|
<MarkerCreator client:only="vue" mapKey={mapKey} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{markers.map((m, index) => (
|
<div style="height: 0; width: 0; overflow: hidden;">
|
||||||
<div id="popup-template" data-id={`marker-${index}`} class="tooltip-content">
|
{markers.map((m, index) => (
|
||||||
{m.cover && m.coverAuthor && m.coverLink && (
|
<div id="popup-template" data-id={`marker-${index}`} class="tooltip-content">
|
||||||
<figure class={m.coverPortrait ? 'portrait' : 'landscape'}>
|
{m.cover && m.coverAuthor && m.coverLink && (
|
||||||
<img src={`/images/cover/${m.cover}`} alt={m.title} width={m.coverPortrait ? 210 : 420} />
|
<figure class={m.coverPortrait ? 'portrait' : 'landscape'}>
|
||||||
<figcaption>
|
<img src={`/images/cover/${m.cover}`} alt={m.title} width={m.coverPortrait ? 210 : 420} />
|
||||||
<cite>{byText} <a href={m.coverLink} target="_blank">{m.coverAuthor}</a></cite>
|
<figcaption>
|
||||||
</figcaption>
|
<cite>{byText} <a href={m.coverLink} target="_blank">{m.coverAuthor}</a></cite>
|
||||||
</figure>
|
</figcaption>
|
||||||
)}
|
</figure>
|
||||||
{m.link ? (
|
)}
|
||||||
<a href={m.link} target="_blank" class="title">{m.title}</a>
|
{m.link ? (
|
||||||
) : (
|
<a href={m.link} target="_blank" class="title">{m.title}</a>
|
||||||
<strong class="title">{m.title}</strong>
|
) : (
|
||||||
)}
|
<strong class="title">{m.title}</strong>
|
||||||
{m.description && <p>{m.description}</p>}
|
)}
|
||||||
{m.mapId && (
|
{m.description && <p>{m.description}</p>}
|
||||||
<a href={m.mapId} class="map-link">
|
|
||||||
<span>
|
<div class="tooltip-footer">
|
||||||
{ seeMapText }
|
{m.mapId && (
|
||||||
</span>
|
<div>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><rect width="256" height="256" fill="none"/><line x1="80" y1="112" x2="144" y2="112" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/><circle cx="112" cy="112" r="80" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/><line x1="168.57" y1="168.57" x2="224" y2="224" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/><line x1="112" y1="80" x2="112" y2="144" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/></svg>
|
<a href={m.mapId} class="map-link">
|
||||||
</a>
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><rect width="256" height="256" fill="none"/><line x1="80" y1="112" x2="144" y2="112" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/><circle cx="112" cy="112" r="80" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/><line x1="168.57" y1="168.57" x2="224" y2="224" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/><line x1="112" y1="80" x2="112" y2="144" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/></svg>
|
||||||
)}
|
|
||||||
</div>
|
<span>
|
||||||
))}
|
{ seeMapText }
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<CopyText text={`${Astro.url}?p=${normalizeString(m.title)}`} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script
|
<script
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ export const translations: LanguageDict = {
|
|||||||
'common.title': 'Titre',
|
'common.title': 'Titre',
|
||||||
'common.create': 'Créer',
|
'common.create': 'Créer',
|
||||||
'common.by': 'par',
|
'common.by': 'par',
|
||||||
|
'common.copyLink': 'Copier le lien',
|
||||||
|
'common.copiedLink': 'Lien copié !',
|
||||||
'continents': 'Continents',
|
'continents': 'Continents',
|
||||||
'cities': 'Villes',
|
'cities': 'Villes',
|
||||||
'others': 'Autres',
|
'others': 'Autres',
|
||||||
@@ -36,6 +38,8 @@ export const translations: LanguageDict = {
|
|||||||
'common.title': 'Title',
|
'common.title': 'Title',
|
||||||
'common.create': 'Create',
|
'common.create': 'Create',
|
||||||
'common.by': 'by',
|
'common.by': 'by',
|
||||||
|
'common.copyLink': 'Copy link',
|
||||||
|
'common.copiedLink': 'Link copied!',
|
||||||
'continents': 'Continents',
|
'continents': 'Continents',
|
||||||
'cities': 'Cities',
|
'cities': 'Cities',
|
||||||
'others': 'Others',
|
'others': 'Others',
|
||||||
|
|||||||
8
src/utils/Strings.ts
Normal file
8
src/utils/Strings.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
/**
|
||||||
|
* Normalize a string to be used as a key
|
||||||
|
* @param str
|
||||||
|
*/
|
||||||
|
export function normalizeString(str: string) {
|
||||||
|
if (!str) return ''
|
||||||
|
return str.normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/[^a-zA-Z0-9]/g, '-').toLowerCase()
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user