@jjlmoya/utils-developer 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +64 -0
- package/src/category/i18n/en.ts +34 -0
- package/src/category/i18n/es.ts +34 -0
- package/src/category/i18n/fr.ts +34 -0
- package/src/category/index.ts +31 -0
- package/src/category/seo.astro +15 -0
- package/src/components/PreviewNavSidebar.astro +116 -0
- package/src/components/PreviewToolbar.astro +143 -0
- package/src/data.ts +11 -0
- package/src/env.d.ts +5 -0
- package/src/index.ts +78 -0
- package/src/layouts/PreviewLayout.astro +117 -0
- package/src/pages/[locale]/[slug].astro +146 -0
- package/src/pages/[locale].astro +251 -0
- package/src/pages/index.astro +4 -0
- package/src/tests/css_specificity_logic.test.ts +39 -0
- package/src/tests/faq_count.test.ts +19 -0
- package/src/tests/locale_completeness.test.ts +42 -0
- package/src/tests/mocks/astro_mock.js +2 -0
- package/src/tests/no_h1_in_components.test.ts +48 -0
- package/src/tests/schemas_fulfillment.test.ts +23 -0
- package/src/tests/seo_length.test.ts +22 -0
- package/src/tests/title_quality.test.ts +55 -0
- package/src/tests/tool_validation.test.ts +17 -0
- package/src/tool/aspectRatio/bibliography.astro +14 -0
- package/src/tool/aspectRatio/component.astro +468 -0
- package/src/tool/aspectRatio/i18n/en.ts +177 -0
- package/src/tool/aspectRatio/i18n/es.ts +177 -0
- package/src/tool/aspectRatio/i18n/fr.ts +177 -0
- package/src/tool/aspectRatio/index.ts +34 -0
- package/src/tool/aspectRatio/seo.astro +14 -0
- package/src/tool/aspectRatio/ui.ts +12 -0
- package/src/tool/calculadoraTiempoDatos/bibliography.astro +14 -0
- package/src/tool/calculadoraTiempoDatos/component.astro +829 -0
- package/src/tool/calculadoraTiempoDatos/i18n/en.ts +150 -0
- package/src/tool/calculadoraTiempoDatos/i18n/es.ts +163 -0
- package/src/tool/calculadoraTiempoDatos/i18n/fr.ts +150 -0
- package/src/tool/calculadoraTiempoDatos/index.ts +33 -0
- package/src/tool/calculadoraTiempoDatos/seo.astro +14 -0
- package/src/tool/calculadoraTiempoDatos/ui.ts +24 -0
- package/src/tool/colorConverter/bibliography.astro +14 -0
- package/src/tool/colorConverter/component.astro +504 -0
- package/src/tool/colorConverter/i18n/en.ts +126 -0
- package/src/tool/colorConverter/i18n/es.ts +126 -0
- package/src/tool/colorConverter/i18n/fr.ts +126 -0
- package/src/tool/colorConverter/index.ts +33 -0
- package/src/tool/colorConverter/seo.astro +14 -0
- package/src/tool/colorConverter/ui.ts +16 -0
- package/src/tool/conversorExcelCsvHtml/bibliography.astro +14 -0
- package/src/tool/conversorExcelCsvHtml/component.astro +595 -0
- package/src/tool/conversorExcelCsvHtml/i18n/en.ts +129 -0
- package/src/tool/conversorExcelCsvHtml/i18n/es.ts +129 -0
- package/src/tool/conversorExcelCsvHtml/i18n/fr.ts +129 -0
- package/src/tool/conversorExcelCsvHtml/index.ts +33 -0
- package/src/tool/conversorExcelCsvHtml/seo.astro +14 -0
- package/src/tool/conversorExcelCsvHtml/ui.ts +14 -0
- package/src/tool/cronGenerator/bibliography.astro +14 -0
- package/src/tool/cronGenerator/component.astro +667 -0
- package/src/tool/cronGenerator/i18n/en.ts +191 -0
- package/src/tool/cronGenerator/i18n/es.ts +191 -0
- package/src/tool/cronGenerator/i18n/fr.ts +191 -0
- package/src/tool/cronGenerator/index.ts +34 -0
- package/src/tool/cronGenerator/seo.astro +14 -0
- package/src/tool/cronGenerator/ui.ts +45 -0
- package/src/tool/cssSpecificityCalculator/bibliography.astro +14 -0
- package/src/tool/cssSpecificityCalculator/component.astro +292 -0
- package/src/tool/cssSpecificityCalculator/i18n/en.ts +168 -0
- package/src/tool/cssSpecificityCalculator/i18n/es.ts +168 -0
- package/src/tool/cssSpecificityCalculator/i18n/fr.ts +168 -0
- package/src/tool/cssSpecificityCalculator/index.ts +34 -0
- package/src/tool/cssSpecificityCalculator/logic.ts +100 -0
- package/src/tool/cssSpecificityCalculator/seo.astro +14 -0
- package/src/tool/cssSpecificityCalculator/ui-controller.ts +57 -0
- package/src/tool/cssSpecificityCalculator/ui.ts +11 -0
- package/src/tool/cssToInlineConverter/bibliography.astro +14 -0
- package/src/tool/cssToInlineConverter/component.astro +308 -0
- package/src/tool/cssToInlineConverter/i18n/en.ts +160 -0
- package/src/tool/cssToInlineConverter/i18n/es.ts +160 -0
- package/src/tool/cssToInlineConverter/i18n/fr.ts +160 -0
- package/src/tool/cssToInlineConverter/index.ts +34 -0
- package/src/tool/cssToInlineConverter/seo.astro +14 -0
- package/src/tool/cssToInlineConverter/ui.ts +12 -0
- package/src/tool/duplicateCssRemover/bibliography.astro +14 -0
- package/src/tool/duplicateCssRemover/component.astro +353 -0
- package/src/tool/duplicateCssRemover/i18n/en.ts +167 -0
- package/src/tool/duplicateCssRemover/i18n/es.ts +167 -0
- package/src/tool/duplicateCssRemover/i18n/fr.ts +167 -0
- package/src/tool/duplicateCssRemover/index.ts +34 -0
- package/src/tool/duplicateCssRemover/seo.astro +14 -0
- package/src/tool/duplicateCssRemover/ui.ts +12 -0
- package/src/tool/generadorSecurityTxt/bibliography.astro +14 -0
- package/src/tool/generadorSecurityTxt/component.astro +473 -0
- package/src/tool/generadorSecurityTxt/i18n/en.ts +173 -0
- package/src/tool/generadorSecurityTxt/i18n/es.ts +173 -0
- package/src/tool/generadorSecurityTxt/i18n/fr.ts +173 -0
- package/src/tool/generadorSecurityTxt/index.ts +33 -0
- package/src/tool/generadorSecurityTxt/seo.astro +14 -0
- package/src/tool/generadorSecurityTxt/ui.ts +27 -0
- package/src/tool/hashGenerator/bibliography.astro +14 -0
- package/src/tool/hashGenerator/component.astro +367 -0
- package/src/tool/hashGenerator/i18n/en.ts +118 -0
- package/src/tool/hashGenerator/i18n/es.ts +118 -0
- package/src/tool/hashGenerator/i18n/fr.ts +118 -0
- package/src/tool/hashGenerator/index.ts +33 -0
- package/src/tool/hashGenerator/md5.ts +138 -0
- package/src/tool/hashGenerator/seo.astro +14 -0
- package/src/tool/hashGenerator/ui.ts +11 -0
- package/src/tool/inspectorCertificadosSsl/bibliography.astro +14 -0
- package/src/tool/inspectorCertificadosSsl/component.astro +580 -0
- package/src/tool/inspectorCertificadosSsl/i18n/en.ts +214 -0
- package/src/tool/inspectorCertificadosSsl/i18n/es.ts +214 -0
- package/src/tool/inspectorCertificadosSsl/i18n/fr.ts +214 -0
- package/src/tool/inspectorCertificadosSsl/index.ts +33 -0
- package/src/tool/inspectorCertificadosSsl/seo.astro +14 -0
- package/src/tool/inspectorCertificadosSsl/ui.ts +29 -0
- package/src/tool/jsonFormatter/bibliography.astro +14 -0
- package/src/tool/jsonFormatter/component.astro +590 -0
- package/src/tool/jsonFormatter/i18n/en.ts +156 -0
- package/src/tool/jsonFormatter/i18n/es.ts +156 -0
- package/src/tool/jsonFormatter/i18n/fr.ts +156 -0
- package/src/tool/jsonFormatter/index.ts +34 -0
- package/src/tool/jsonFormatter/seo.astro +14 -0
- package/src/tool/jsonFormatter/ui.ts +15 -0
- package/src/tool/keycode/bibliography.astro +14 -0
- package/src/tool/keycode/component.astro +540 -0
- package/src/tool/keycode/i18n/en.ts +160 -0
- package/src/tool/keycode/i18n/es.ts +160 -0
- package/src/tool/keycode/i18n/fr.ts +160 -0
- package/src/tool/keycode/index.ts +34 -0
- package/src/tool/keycode/seo.astro +14 -0
- package/src/tool/keycode/ui.ts +10 -0
- package/src/tool/llmCostCalculator/bibliography.astro +14 -0
- package/src/tool/llmCostCalculator/component.astro +488 -0
- package/src/tool/llmCostCalculator/data.ts +99 -0
- package/src/tool/llmCostCalculator/i18n/en.ts +165 -0
- package/src/tool/llmCostCalculator/i18n/es.ts +165 -0
- package/src/tool/llmCostCalculator/i18n/fr.ts +165 -0
- package/src/tool/llmCostCalculator/index.ts +34 -0
- package/src/tool/llmCostCalculator/seo.astro +14 -0
- package/src/tool/llmCostCalculator/ui.ts +11 -0
- package/src/tool/mobileMockupGenerator/app.ts +614 -0
- package/src/tool/mobileMockupGenerator/bibliography.astro +14 -0
- package/src/tool/mobileMockupGenerator/component.astro +1044 -0
- package/src/tool/mobileMockupGenerator/constants.ts +48 -0
- package/src/tool/mobileMockupGenerator/i18n/en.ts +121 -0
- package/src/tool/mobileMockupGenerator/i18n/es.ts +121 -0
- package/src/tool/mobileMockupGenerator/i18n/fr.ts +121 -0
- package/src/tool/mobileMockupGenerator/index.ts +33 -0
- package/src/tool/mobileMockupGenerator/rendering.ts +189 -0
- package/src/tool/mobileMockupGenerator/seo.astro +14 -0
- package/src/tool/mobileMockupGenerator/store.ts +145 -0
- package/src/tool/mobileMockupGenerator/types.ts +41 -0
- package/src/tool/mobileMockupGenerator/ui.ts +48 -0
- package/src/tool/musicalTypography/bibliography.astro +14 -0
- package/src/tool/musicalTypography/component.astro +480 -0
- package/src/tool/musicalTypography/i18n/en.ts +179 -0
- package/src/tool/musicalTypography/i18n/es.ts +179 -0
- package/src/tool/musicalTypography/i18n/fr.ts +179 -0
- package/src/tool/musicalTypography/index.ts +33 -0
- package/src/tool/musicalTypography/seo.astro +14 -0
- package/src/tool/musicalTypography/ui.ts +25 -0
- package/src/tool/placeholderGenerator/bibliography.astro +14 -0
- package/src/tool/placeholderGenerator/component.astro +499 -0
- package/src/tool/placeholderGenerator/i18n/en.ts +176 -0
- package/src/tool/placeholderGenerator/i18n/es.ts +176 -0
- package/src/tool/placeholderGenerator/i18n/fr.ts +176 -0
- package/src/tool/placeholderGenerator/index.ts +34 -0
- package/src/tool/placeholderGenerator/seo.astro +14 -0
- package/src/tool/placeholderGenerator/ui.ts +14 -0
- package/src/tool/promptLibrary/bibliography.astro +14 -0
- package/src/tool/promptLibrary/component.astro +1131 -0
- package/src/tool/promptLibrary/controller.ts +49 -0
- package/src/tool/promptLibrary/i18n/en.ts +131 -0
- package/src/tool/promptLibrary/i18n/es.ts +131 -0
- package/src/tool/promptLibrary/i18n/fr.ts +126 -0
- package/src/tool/promptLibrary/index.ts +33 -0
- package/src/tool/promptLibrary/seo.astro +14 -0
- package/src/tool/promptLibrary/ui.ts +29 -0
- package/src/tool/readabilityCalculator/bibliography.astro +14 -0
- package/src/tool/readabilityCalculator/component.astro +534 -0
- package/src/tool/readabilityCalculator/i18n/en.ts +133 -0
- package/src/tool/readabilityCalculator/i18n/es.ts +133 -0
- package/src/tool/readabilityCalculator/i18n/fr.ts +133 -0
- package/src/tool/readabilityCalculator/index.ts +33 -0
- package/src/tool/readabilityCalculator/seo.astro +14 -0
- package/src/tool/readabilityCalculator/ui.ts +27 -0
- package/src/tool/svgSanitizer/bibliography.astro +14 -0
- package/src/tool/svgSanitizer/component.astro +554 -0
- package/src/tool/svgSanitizer/i18n/en.ts +125 -0
- package/src/tool/svgSanitizer/i18n/es.ts +125 -0
- package/src/tool/svgSanitizer/i18n/fr.ts +125 -0
- package/src/tool/svgSanitizer/index.ts +33 -0
- package/src/tool/svgSanitizer/seo.astro +14 -0
- package/src/tool/svgSanitizer/ui.ts +18 -0
- package/src/tool/svgToCss/bibliography.astro +14 -0
- package/src/tool/svgToCss/component.astro +381 -0
- package/src/tool/svgToCss/i18n/en.ts +174 -0
- package/src/tool/svgToCss/i18n/es.ts +174 -0
- package/src/tool/svgToCss/i18n/fr.ts +174 -0
- package/src/tool/svgToCss/index.ts +34 -0
- package/src/tool/svgToCss/seo.astro +14 -0
- package/src/tool/svgToCss/ui.ts +13 -0
- package/src/tool/urlCleaner/bibliography.astro +14 -0
- package/src/tool/urlCleaner/component.astro +458 -0
- package/src/tool/urlCleaner/i18n/en.ts +116 -0
- package/src/tool/urlCleaner/i18n/es.ts +116 -0
- package/src/tool/urlCleaner/i18n/fr.ts +116 -0
- package/src/tool/urlCleaner/index.ts +33 -0
- package/src/tool/urlCleaner/seo.astro +14 -0
- package/src/tool/urlCleaner/ui.ts +12 -0
- package/src/tool/urlEncoderDecoder/bibliography.astro +14 -0
- package/src/tool/urlEncoderDecoder/component.astro +291 -0
- package/src/tool/urlEncoderDecoder/i18n/en.ts +162 -0
- package/src/tool/urlEncoderDecoder/i18n/es.ts +162 -0
- package/src/tool/urlEncoderDecoder/i18n/fr.ts +162 -0
- package/src/tool/urlEncoderDecoder/index.ts +34 -0
- package/src/tool/urlEncoderDecoder/seo.astro +14 -0
- package/src/tool/urlEncoderDecoder/ui.ts +11 -0
- package/src/tool/utmGenerator/bibliography.astro +14 -0
- package/src/tool/utmGenerator/component.astro +270 -0
- package/src/tool/utmGenerator/i18n/en.ts +117 -0
- package/src/tool/utmGenerator/i18n/es.ts +117 -0
- package/src/tool/utmGenerator/i18n/fr.ts +117 -0
- package/src/tool/utmGenerator/index.ts +33 -0
- package/src/tool/utmGenerator/seo.astro +14 -0
- package/src/tool/utmGenerator/ui.ts +13 -0
- package/src/tools.ts +27 -0
- package/src/types.ts +72 -0
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import type { WithContext, FAQPage, HowToThing, SoftwareApplication } from 'schema-dts';
|
|
2
|
+
import type { ToolLocaleContent } from '../../../types';
|
|
3
|
+
import type { UrlEncoderDecoderUI } from '../ui';
|
|
4
|
+
|
|
5
|
+
const slug = 'codificador-decodificador-url';
|
|
6
|
+
const title = 'Codificador y Decodificador de URLs Online';
|
|
7
|
+
const description =
|
|
8
|
+
'Convierte caracteres especiales de cualquier enlace a formato seguro (Percent-Encoding) o devuélvelos a su estado original legible al instante de forma local.';
|
|
9
|
+
|
|
10
|
+
const faqData = [
|
|
11
|
+
{
|
|
12
|
+
question: '¿Qué caracteres se codifican en una URL?',
|
|
13
|
+
answer:
|
|
14
|
+
'Se codifican todos los caracteres no permitidos en el estándar ASCII para URLs: espacios, tildes, ñ, símbolos como &, =, +, #, ?, /, entre otros. Por ejemplo, un espacio se convierte en %20 y la ñ en %C3%B1.',
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
question: '¿Cuál es la diferencia entre encodeURI y encodeURIComponent?',
|
|
18
|
+
answer:
|
|
19
|
+
'encodeURI codifica una URL completa y deja intactos los caracteres reservados como / y ?. encodeURIComponent codifica todos los caracteres especiales incluyendo los reservados, siendo ideal para codificar valores individuales de parámetros de consulta.',
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
question: '¿Por qué mi URL tiene %20 en lugar de espacios?',
|
|
23
|
+
answer:
|
|
24
|
+
'El protocolo HTTP no permite espacios en las URLs. El %20 es la representación en Percent-Encoding del espacio en blanco según el estándar ASCII. Algunos sistemas usan el signo + como alternativa, pero %20 es el más universal y seguro.',
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
question: '¿Es seguro usar esta herramienta con URLs privadas?',
|
|
28
|
+
answer:
|
|
29
|
+
'Sí, completamente seguro. Todo el procesamiento ocurre en tu navegador mediante JavaScript nativo (encodeURIComponent/decodeURIComponent). Ninguna de tus URLs o parámetros se envían a ningún servidor externo.',
|
|
30
|
+
},
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
const howToData = [
|
|
34
|
+
{
|
|
35
|
+
name: 'Pegar el texto crudo',
|
|
36
|
+
text: 'Escribe o pega tu URL o cadena de texto en el campo superior "Texto Crudo (Legible)".',
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: 'Codificar o decodificar',
|
|
40
|
+
text: 'Haz clic en "Codificar URL" para convertir el texto a formato seguro Percent-Encoding, o en "Decodificar" para revertir una URL codificada a su forma legible.',
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: 'Copiar el resultado',
|
|
44
|
+
text: 'Usa el botón "Copiar" del campo correspondiente para llevar el resultado a tu portapapeles.',
|
|
45
|
+
},
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
const faqSchema: WithContext<FAQPage> = {
|
|
49
|
+
'@context': 'https://schema.org',
|
|
50
|
+
'@type': 'FAQPage',
|
|
51
|
+
mainEntity: faqData.map((item) => ({
|
|
52
|
+
'@type': 'Question',
|
|
53
|
+
name: item.question,
|
|
54
|
+
acceptedAnswer: { '@type': 'Answer', text: item.answer },
|
|
55
|
+
})),
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const howToSchema: WithContext<HowToThing> = {
|
|
59
|
+
'@context': 'https://schema.org',
|
|
60
|
+
'@type': 'HowTo',
|
|
61
|
+
name: title,
|
|
62
|
+
description,
|
|
63
|
+
step: howToData.map((step, i) => ({
|
|
64
|
+
'@type': 'HowToStep',
|
|
65
|
+
position: i + 1,
|
|
66
|
+
name: step.name,
|
|
67
|
+
text: step.text,
|
|
68
|
+
})),
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const appSchema: WithContext<SoftwareApplication> = {
|
|
72
|
+
'@context': 'https://schema.org',
|
|
73
|
+
'@type': 'SoftwareApplication',
|
|
74
|
+
name: title,
|
|
75
|
+
description,
|
|
76
|
+
applicationCategory: 'UtilityApplication',
|
|
77
|
+
operatingSystem: 'All',
|
|
78
|
+
offers: { '@type': 'Offer', price: '0', priceCurrency: 'EUR' },
|
|
79
|
+
inLanguage: 'es',
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const ui: UrlEncoderDecoderUI = {
|
|
83
|
+
labelRaw: 'Texto Crudo (Legible)',
|
|
84
|
+
labelEncoded: 'URL Formateada (Encoded)',
|
|
85
|
+
btnClear: 'Limpiar',
|
|
86
|
+
btnCopy: 'Copiar',
|
|
87
|
+
btnCopied: '¡Copiado!',
|
|
88
|
+
btnEncode: 'Codificar URL',
|
|
89
|
+
btnDecode: 'Decodificar',
|
|
90
|
+
placeholderRaw: 'https://ejemplo.com/busqueda?q=zapatos de tacón rojo&talla=38',
|
|
91
|
+
placeholderEncoded: 'https%3A%2F%2Fejemplo.com%2Fbusqueda%3Fq%3Dzapatos%20de%20tac%C3%B3n%20rojo%26talla%3D38',
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
export const content: ToolLocaleContent<UrlEncoderDecoderUI> = {
|
|
95
|
+
slug,
|
|
96
|
+
title,
|
|
97
|
+
description,
|
|
98
|
+
ui,
|
|
99
|
+
faqTitle: 'Preguntas Frecuentes',
|
|
100
|
+
faq: faqData,
|
|
101
|
+
bibliographyTitle: 'Referencias y Documentación',
|
|
102
|
+
bibliography: [
|
|
103
|
+
{
|
|
104
|
+
name: 'MDN Web Docs: encodeURIComponent()',
|
|
105
|
+
url: 'https://developer.mozilla.org/es/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent',
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
name: 'IETF RFC 3986: Estructura de URIs',
|
|
109
|
+
url: 'https://datatracker.ietf.org/doc/html/rfc3986',
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
name: 'W3C: URL Living Standard',
|
|
113
|
+
url: 'https://url.spec.whatwg.org/',
|
|
114
|
+
},
|
|
115
|
+
],
|
|
116
|
+
howTo: howToData,
|
|
117
|
+
schemas: [faqSchema, howToSchema, appSchema],
|
|
118
|
+
seo: [
|
|
119
|
+
{
|
|
120
|
+
type: 'title',
|
|
121
|
+
text: '¿Qué es la Codificación de una URL?',
|
|
122
|
+
level: 2,
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
type: 'paragraph',
|
|
126
|
+
html: 'Al navegar por internet o enviar peticiones a servidores, es común pensar en una URL (Uniform Resource Locator) como simplemente una "dirección web". Sin embargo, el protocolo de internet dicta que las URLs solo pueden transmitirse utilizando un conjunto muy restringido de caracteres <strong>ASCII estándar</strong>.',
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
type: 'paragraph',
|
|
130
|
+
html: '¿Qué ocurre si la URL contiene un espacio, tildes, o parámetros especiales como símbolos de más (<code>+</code>) o de igual (<code>=</code>)? Para evitar que los sistemas colapsen intentando leer caracteres ilegales, estos deben ser traducidos a su forma segura compatible utilizando <strong>Percent-Encoding</strong> (Codificación porcentual).',
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
type: 'title',
|
|
134
|
+
text: 'Cómo funciona el Percent-Encoding',
|
|
135
|
+
level: 3,
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
type: 'paragraph',
|
|
139
|
+
html: 'Cuando utilizas esta herramienta, el algoritmo toma cualquier carácter "inseguro" (como un espacio en blanco o una letra acentuada como la ñ) y lo sustituye por un signo de porcentaje <code>%</code> seguido de dos dígitos hexadecimales correspondientes a su valor en el estándar UTF-8.',
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
type: 'list',
|
|
143
|
+
items: [
|
|
144
|
+
'<strong>Ejemplo Básico:</strong> Un simple espacio en blanco será sustituido por su equivalente seguro: <code>%20</code>.',
|
|
145
|
+
'<strong>Soporte Extendido:</strong> Una letra <code>á</code> se transformará en <code>%C3%A1</code>, y la <code>ñ</code> en <code>%C3%B1</code>.',
|
|
146
|
+
],
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
type: 'title',
|
|
150
|
+
text: 'La importancia en APIs y Consultas GET',
|
|
151
|
+
level: 3,
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
type: 'paragraph',
|
|
155
|
+
html: 'Cuando desarrollas integraciones, un error típico consiste en pasar una cadena cruda a los parámetros de una URL. Si insertas <code>camisa&azul</code> de forma pura a tu backend (<code>/buscar?q=camisa&azul</code>), el servidor interpretará que <code>azul</code> es un nuevo parámetro, rompiendo toda la lógica del código.',
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
type: 'paragraph',
|
|
159
|
+
html: 'Esta herramienta garantiza cálculos limpios y automáticos con un 100% de ejecución en tu navegador local. Ninguna de tus cadenas URL es transmitida a servidores de terceros, asegurando la privacidad de tus tokens y parámetros analíticos.',
|
|
160
|
+
},
|
|
161
|
+
],
|
|
162
|
+
};
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import type { WithContext, FAQPage, HowToThing, SoftwareApplication } from 'schema-dts';
|
|
2
|
+
import type { ToolLocaleContent } from '../../../types';
|
|
3
|
+
import type { UrlEncoderDecoderUI } from '../ui';
|
|
4
|
+
|
|
5
|
+
const slug = 'encodeur-decodeur-url';
|
|
6
|
+
const title = "Encodeur et Décodeur d'URL en Ligne";
|
|
7
|
+
const description =
|
|
8
|
+
"Convertissez les caractères spéciaux de n'importe quel lien en format sécurisé (Percent-Encoding) ou restituez-les à leur état original lisible instantanément et localement.";
|
|
9
|
+
|
|
10
|
+
const faqData = [
|
|
11
|
+
{
|
|
12
|
+
question: 'Quels caractères sont encodés dans une URL ?',
|
|
13
|
+
answer:
|
|
14
|
+
"Tous les caractères non autorisés dans le standard ASCII pour les URLs sont encodés : espaces, lettres accentuées, symboles tels que &, =, +, #, ?, / et d'autres. Par exemple, un espace devient %20 et ñ devient %C3%B1.",
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
question: 'Quelle est la différence entre encodeURI et encodeURIComponent ?',
|
|
18
|
+
answer:
|
|
19
|
+
"encodeURI encode une URL complète et laisse intacts les caractères réservés comme / et ?. encodeURIComponent encode tous les caractères spéciaux y compris les réservés, ce qui le rend idéal pour encoder des valeurs individuelles de paramètres de requête.",
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
question: "Pourquoi mon URL a-t-elle %20 à la place des espaces ?",
|
|
23
|
+
answer:
|
|
24
|
+
"Le protocole HTTP n'autorise pas les espaces dans les URLs. %20 est la représentation en Percent-Encoding d'un espace selon le standard ASCII. Certains systèmes utilisent le signe + comme alternative, mais %20 est le plus universel et sûr.",
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
question: "Est-il sûr d'utiliser cet outil avec des URLs privées ?",
|
|
28
|
+
answer:
|
|
29
|
+
"Oui, complètement sûr. Tout le traitement se fait dans votre navigateur en utilisant JavaScript natif (encodeURIComponent/decodeURIComponent). Aucune de vos URLs ou paramètres n'est envoyé à un serveur externe.",
|
|
30
|
+
},
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
const howToData = [
|
|
34
|
+
{
|
|
35
|
+
name: 'Coller le texte brut',
|
|
36
|
+
text: 'Tapez ou collez votre URL ou chaîne de texte dans le champ supérieur "Texte Brut (Lisible)".',
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: 'Encoder ou décoder',
|
|
40
|
+
text: 'Cliquez sur "Encoder URL" pour convertir le texte en format Percent-Encoding sécurisé, ou sur "Décoder" pour revenir à la forme lisible d\'une URL encodée.',
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: 'Copier le résultat',
|
|
44
|
+
text: 'Utilisez le bouton "Copier" du champ correspondant pour transférer le résultat dans votre presse-papiers.',
|
|
45
|
+
},
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
const faqSchema: WithContext<FAQPage> = {
|
|
49
|
+
'@context': 'https://schema.org',
|
|
50
|
+
'@type': 'FAQPage',
|
|
51
|
+
mainEntity: faqData.map((item) => ({
|
|
52
|
+
'@type': 'Question',
|
|
53
|
+
name: item.question,
|
|
54
|
+
acceptedAnswer: { '@type': 'Answer', text: item.answer },
|
|
55
|
+
})),
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const howToSchema: WithContext<HowToThing> = {
|
|
59
|
+
'@context': 'https://schema.org',
|
|
60
|
+
'@type': 'HowTo',
|
|
61
|
+
name: title,
|
|
62
|
+
description,
|
|
63
|
+
step: howToData.map((step, i) => ({
|
|
64
|
+
'@type': 'HowToStep',
|
|
65
|
+
position: i + 1,
|
|
66
|
+
name: step.name,
|
|
67
|
+
text: step.text,
|
|
68
|
+
})),
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const appSchema: WithContext<SoftwareApplication> = {
|
|
72
|
+
'@context': 'https://schema.org',
|
|
73
|
+
'@type': 'SoftwareApplication',
|
|
74
|
+
name: title,
|
|
75
|
+
description,
|
|
76
|
+
applicationCategory: 'UtilityApplication',
|
|
77
|
+
operatingSystem: 'All',
|
|
78
|
+
offers: { '@type': 'Offer', price: '0', priceCurrency: 'EUR' },
|
|
79
|
+
inLanguage: 'fr',
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const ui: UrlEncoderDecoderUI = {
|
|
83
|
+
labelRaw: 'Texte Brut (Lisible)',
|
|
84
|
+
labelEncoded: 'URL Formatée (Encodée)',
|
|
85
|
+
btnClear: 'Effacer',
|
|
86
|
+
btnCopy: 'Copier',
|
|
87
|
+
btnCopied: 'Copié !',
|
|
88
|
+
btnEncode: "Encoder l'URL",
|
|
89
|
+
btnDecode: 'Décoder',
|
|
90
|
+
placeholderRaw: 'https://exemple.com/recherche?q=chaussures à talons rouges&taille=38',
|
|
91
|
+
placeholderEncoded: 'https%3A%2F%2Fexemple.com%2Frecherche%3Fq%3Dchaussures%20%C3%A0%20talons%20rouges%26taille%3D38',
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
export const content: ToolLocaleContent<UrlEncoderDecoderUI> = {
|
|
95
|
+
slug,
|
|
96
|
+
title,
|
|
97
|
+
description,
|
|
98
|
+
ui,
|
|
99
|
+
faqTitle: 'Questions Fréquentes',
|
|
100
|
+
faq: faqData,
|
|
101
|
+
bibliographyTitle: 'Références et Documentation',
|
|
102
|
+
bibliography: [
|
|
103
|
+
{
|
|
104
|
+
name: 'MDN Web Docs: encodeURIComponent()',
|
|
105
|
+
url: 'https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent',
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
name: 'IETF RFC 3986: Syntaxe URI',
|
|
109
|
+
url: 'https://datatracker.ietf.org/doc/html/rfc3986',
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
name: 'W3C: URL Living Standard',
|
|
113
|
+
url: 'https://url.spec.whatwg.org/',
|
|
114
|
+
},
|
|
115
|
+
],
|
|
116
|
+
howTo: howToData,
|
|
117
|
+
schemas: [faqSchema, howToSchema, appSchema],
|
|
118
|
+
seo: [
|
|
119
|
+
{
|
|
120
|
+
type: 'title',
|
|
121
|
+
text: "Qu'est-ce que l'Encodage d'URL ?",
|
|
122
|
+
level: 2,
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
type: 'paragraph',
|
|
126
|
+
html: "Lorsqu'on navigue sur internet ou qu'on envoie des requêtes à des serveurs, il est courant de penser à une URL (Uniform Resource Locator) comme simplement une \"adresse web\". Cependant, le protocole internet stipule que les URLs ne peuvent être transmises qu'en utilisant un ensemble très restreint de caractères <strong>ASCII standard</strong>.",
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
type: 'paragraph',
|
|
130
|
+
html: "Que se passe-t-il si l'URL contient un espace, des lettres accentuées ou des paramètres spéciaux comme les symboles plus (<code>+</code>) ou égal (<code>=</code>) ? Pour éviter que les systèmes s'effondrent en tentant de lire des caractères illégaux, ceux-ci doivent être traduits dans leur forme sécurisée compatible en utilisant le <strong>Percent-Encoding</strong>.",
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
type: 'title',
|
|
134
|
+
text: 'Comment fonctionne le Percent-Encoding',
|
|
135
|
+
level: 3,
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
type: 'paragraph',
|
|
139
|
+
html: "Lorsque vous utilisez cet outil, l'algorithme prend tout caractère \"non sécurisé\" (comme un espace ou une lettre accentuée comme le ñ) et le remplace par un signe pourcentage <code>%</code> suivi de deux chiffres hexadécimaux correspondant à sa valeur dans le standard UTF-8.",
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
type: 'list',
|
|
143
|
+
items: [
|
|
144
|
+
'<strong>Exemple Basique :</strong> Un simple espace sera remplacé par son équivalent sécurisé : <code>%20</code>.',
|
|
145
|
+
'<strong>Support Étendu :</strong> La lettre <code>á</code> devient <code>%C3%A1</code>, et <code>ñ</code> devient <code>%C3%B1</code>.',
|
|
146
|
+
],
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
type: 'title',
|
|
150
|
+
text: "L'Importance dans les APIs et Requêtes GET",
|
|
151
|
+
level: 3,
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
type: 'paragraph',
|
|
155
|
+
html: "Lors du développement d'intégrations, une erreur typique consiste à passer une chaîne brute aux paramètres d'une URL. Si vous insérez <code>chemise&bleu</code> de façon pure dans votre backend (<code>/chercher?q=chemise&bleu</code>), le serveur interprétera que <code>bleu</code> est un nouveau paramètre, brisant toute la logique du code.",
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
type: 'paragraph',
|
|
159
|
+
html: "Cet outil garantit des calculs propres et automatiques avec une exécution à 100% dans votre navigateur local. Aucune de vos chaînes URL n'est transmise à des serveurs tiers, assurant la confidentialité de vos tokens et paramètres analytiques.",
|
|
160
|
+
},
|
|
161
|
+
],
|
|
162
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { DeveloperToolEntry, ToolLocaleContent, ToolDefinition } from '../../types';
|
|
2
|
+
import UrlEncoderDecoderComponent from './component.astro';
|
|
3
|
+
import UrlEncoderDecoderSEO from './seo.astro';
|
|
4
|
+
import UrlEncoderDecoderBibliography from './bibliography.astro';
|
|
5
|
+
|
|
6
|
+
import type { UrlEncoderDecoderUI } from './ui';
|
|
7
|
+
|
|
8
|
+
export type UrlEncoderDecoderLocaleContent = ToolLocaleContent<UrlEncoderDecoderUI>;
|
|
9
|
+
|
|
10
|
+
import { content as es } from './i18n/es';
|
|
11
|
+
import { content as en } from './i18n/en';
|
|
12
|
+
import { content as fr } from './i18n/fr';
|
|
13
|
+
|
|
14
|
+
export const urlEncoderDecoder: DeveloperToolEntry<UrlEncoderDecoderUI> = {
|
|
15
|
+
id: 'url-encoder-decoder',
|
|
16
|
+
icons: {
|
|
17
|
+
bg: 'mdi:link-variant',
|
|
18
|
+
fg: 'mdi:code-brackets',
|
|
19
|
+
},
|
|
20
|
+
i18n: {
|
|
21
|
+
es: async () => es,
|
|
22
|
+
en: async () => en,
|
|
23
|
+
fr: async () => fr,
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export { UrlEncoderDecoderComponent, UrlEncoderDecoderSEO, UrlEncoderDecoderBibliography };
|
|
28
|
+
|
|
29
|
+
export const URL_ENCODER_DECODER_TOOL: ToolDefinition = {
|
|
30
|
+
entry: urlEncoderDecoder,
|
|
31
|
+
Component: UrlEncoderDecoderComponent,
|
|
32
|
+
SEOComponent: UrlEncoderDecoderSEO,
|
|
33
|
+
BibliographyComponent: UrlEncoderDecoderBibliography,
|
|
34
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { SEORenderer } from '@jjlmoya/utils-shared';
|
|
3
|
+
import { urlEncoderDecoder } from './index';
|
|
4
|
+
import type { KnownLocale } from '../../types';
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
locale?: KnownLocale;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const { locale = 'es' } = Astro.props;
|
|
11
|
+
const content = await urlEncoderDecoder.i18n[locale]?.();
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
{content && <SEORenderer content={{ locale, sections: content.seo }} />}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface UrlEncoderDecoderUI extends Record<string, string> {
|
|
2
|
+
labelRaw: string;
|
|
3
|
+
labelEncoded: string;
|
|
4
|
+
btnClear: string;
|
|
5
|
+
btnCopy: string;
|
|
6
|
+
btnCopied: string;
|
|
7
|
+
btnEncode: string;
|
|
8
|
+
btnDecode: string;
|
|
9
|
+
placeholderRaw: string;
|
|
10
|
+
placeholderEncoded: string;
|
|
11
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { Bibliography as SharedBibliography } from '@jjlmoya/utils-shared';
|
|
3
|
+
import { utmGenerator } from './index';
|
|
4
|
+
import type { KnownLocale } from '../../types';
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
locale?: KnownLocale;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const { locale = 'es' } = Astro.props;
|
|
11
|
+
const content = await utmGenerator.i18n[locale]?.();
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
{content && <SharedBibliography links={content.bibliography} />}
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { UtmGeneratorUI } from './ui';
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
ui?: Record<string, unknown>;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const { ui } = Astro.props;
|
|
9
|
+
const t = (ui ?? {}) as UtmGeneratorUI;
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
<div id="ug-root" class="ug-root" data-i18n={JSON.stringify(t)}>
|
|
13
|
+
<div class="ug-container">
|
|
14
|
+
<div class="ug-grid">
|
|
15
|
+
<div class="ug-input-group ug-col-span-2">
|
|
16
|
+
<label for="ug-url">{t.labelUrl}</label>
|
|
17
|
+
<input type="url" id="ug-url" class="ug-input" placeholder="https://example.com" />
|
|
18
|
+
</div>
|
|
19
|
+
|
|
20
|
+
<div class="ug-input-group">
|
|
21
|
+
<label for="ug-source">{t.labelSource}</label>
|
|
22
|
+
<input type="text" id="ug-source" class="ug-input" placeholder="google" />
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
<div class="ug-input-group">
|
|
26
|
+
<label for="ug-medium">{t.labelMedium}</label>
|
|
27
|
+
<input type="text" id="ug-medium" class="ug-input" placeholder="cpc" />
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
<div class="ug-input-group">
|
|
31
|
+
<label for="ug-campaign">{t.labelCampaign}</label>
|
|
32
|
+
<input type="text" id="ug-campaign" class="ug-input" placeholder="summer_offer" />
|
|
33
|
+
</div>
|
|
34
|
+
|
|
35
|
+
<div class="ug-input-group">
|
|
36
|
+
<label for="ug-term">{t.labelTerm}</label>
|
|
37
|
+
<input type="text" id="ug-term" class="ug-input" placeholder="buy_shoes" />
|
|
38
|
+
</div>
|
|
39
|
+
|
|
40
|
+
<div class="ug-input-group">
|
|
41
|
+
<label for="ug-content">{t.labelContent}</label>
|
|
42
|
+
<input type="text" id="ug-content" class="ug-input" placeholder="banner_top" />
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
<div class="ug-result">
|
|
47
|
+
<div class="ug-result-header">
|
|
48
|
+
<span class="ug-result-label">{t.labelGenerated}</span>
|
|
49
|
+
<button id="ug-btn-copy" class="ug-btn-copy">{t.btnCopy}</button>
|
|
50
|
+
</div>
|
|
51
|
+
<div id="ug-output" class="ug-result-url">https://example.com</div>
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
|
|
56
|
+
<style>
|
|
57
|
+
.ug-root {
|
|
58
|
+
width: 100%;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.ug-container {
|
|
62
|
+
max-width: 800px;
|
|
63
|
+
margin: 0 auto;
|
|
64
|
+
padding: 2rem;
|
|
65
|
+
background: rgba(255, 255, 255, 0.75);
|
|
66
|
+
backdrop-filter: blur(14px);
|
|
67
|
+
-webkit-backdrop-filter: blur(14px);
|
|
68
|
+
border: 1px solid rgba(255, 255, 255, 0.35);
|
|
69
|
+
border-radius: 1.5rem;
|
|
70
|
+
box-shadow: 0 8px 32px rgba(31, 38, 135, 0.06);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
:global(.theme-dark) .ug-container {
|
|
74
|
+
background: rgba(15, 23, 42, 0.75);
|
|
75
|
+
border-color: rgba(255, 255, 255, 0.06);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.ug-grid {
|
|
79
|
+
display: grid;
|
|
80
|
+
grid-template-columns: 1fr 1fr;
|
|
81
|
+
gap: 1.5rem;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
@media (max-width: 640px) {
|
|
85
|
+
.ug-grid {
|
|
86
|
+
grid-template-columns: 1fr;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.ug-grid .ug-col-span-2 {
|
|
90
|
+
grid-column: span 1;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.ug-container {
|
|
94
|
+
padding: 1.5rem;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.ug-input-group {
|
|
99
|
+
display: flex;
|
|
100
|
+
flex-direction: column;
|
|
101
|
+
gap: 0.5rem;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.ug-input-group label {
|
|
105
|
+
font-size: 0.875rem;
|
|
106
|
+
font-weight: 600;
|
|
107
|
+
color: rgb(100, 116, 139);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
:global(.theme-dark) .ug-input-group label {
|
|
111
|
+
color: rgb(148, 163, 184);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.ug-input {
|
|
115
|
+
padding: 0.8rem 1rem;
|
|
116
|
+
border-radius: 0.75rem;
|
|
117
|
+
border: 1px solid rgb(226, 232, 240);
|
|
118
|
+
background: white;
|
|
119
|
+
color: rgb(51, 65, 85);
|
|
120
|
+
transition: all 0.2s ease;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
:global(.theme-dark) .ug-input {
|
|
124
|
+
background: rgba(255, 255, 255, 0.04);
|
|
125
|
+
border-color: rgb(51, 65, 85);
|
|
126
|
+
color: rgb(203, 213, 225);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.ug-input:focus {
|
|
130
|
+
outline: none;
|
|
131
|
+
border-color: rgb(99, 102, 241);
|
|
132
|
+
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.ug-col-span-2 {
|
|
136
|
+
grid-column: span 2;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.ug-result {
|
|
140
|
+
margin-top: 2rem;
|
|
141
|
+
padding: 1.5rem;
|
|
142
|
+
background: rgba(99, 102, 241, 0.1);
|
|
143
|
+
border-radius: 1rem;
|
|
144
|
+
border: 1px solid rgba(99, 102, 241, 0.2);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
:global(.theme-dark) .ug-result {
|
|
148
|
+
background: rgba(99, 102, 241, 0.05);
|
|
149
|
+
border-color: rgba(99, 102, 241, 0.15);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.ug-result-header {
|
|
153
|
+
display: flex;
|
|
154
|
+
justify-content: space-between;
|
|
155
|
+
align-items: center;
|
|
156
|
+
margin-bottom: 0.75rem;
|
|
157
|
+
flex-wrap: wrap;
|
|
158
|
+
gap: 0.5rem;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
.ug-result-label {
|
|
162
|
+
font-weight: 600;
|
|
163
|
+
color: rgb(51, 65, 85);
|
|
164
|
+
font-size: 0.875rem;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
:global(.theme-dark) .ug-result-label {
|
|
168
|
+
color: rgb(203, 213, 225);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
.ug-result-url {
|
|
172
|
+
word-break: break-all;
|
|
173
|
+
font-size: 0.875rem;
|
|
174
|
+
color: rgb(99, 102, 241);
|
|
175
|
+
background: white;
|
|
176
|
+
padding: 1rem;
|
|
177
|
+
border-radius: 0.5rem;
|
|
178
|
+
border: 1px solid rgba(0, 0, 0, 0.05);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
:global(.theme-dark) .ug-result-url {
|
|
182
|
+
background: rgb(30, 41, 59);
|
|
183
|
+
border-color: rgb(51, 65, 85);
|
|
184
|
+
color: rgb(129, 140, 248);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
.ug-btn-copy {
|
|
188
|
+
padding: 0.5rem 1rem;
|
|
189
|
+
background: rgb(99, 102, 241);
|
|
190
|
+
color: white;
|
|
191
|
+
border: none;
|
|
192
|
+
border-radius: 0.5rem;
|
|
193
|
+
font-weight: 600;
|
|
194
|
+
font-size: 0.875rem;
|
|
195
|
+
cursor: pointer;
|
|
196
|
+
transition: all 0.2s ease;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
.ug-btn-copy:hover {
|
|
200
|
+
opacity: 0.9;
|
|
201
|
+
transform: translateY(-1px);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
.ug-btn-copy:active {
|
|
205
|
+
transform: translateY(0);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
.ug-btn-copy.ug-copied {
|
|
209
|
+
background: rgb(16, 185, 129);
|
|
210
|
+
}
|
|
211
|
+
</style>
|
|
212
|
+
|
|
213
|
+
<script>
|
|
214
|
+
import type { UtmGeneratorUI } from './ui';
|
|
215
|
+
|
|
216
|
+
const root = document.getElementById('ug-root') as HTMLElement;
|
|
217
|
+
const t = JSON.parse(root.dataset.i18n ?? '{}') as UtmGeneratorUI;
|
|
218
|
+
|
|
219
|
+
const urlInput = document.getElementById('ug-url') as HTMLInputElement;
|
|
220
|
+
const sourceInput = document.getElementById('ug-source') as HTMLInputElement;
|
|
221
|
+
const mediumInput = document.getElementById('ug-medium') as HTMLInputElement;
|
|
222
|
+
const campaignInput = document.getElementById('ug-campaign') as HTMLInputElement;
|
|
223
|
+
const termInput = document.getElementById('ug-term') as HTMLInputElement;
|
|
224
|
+
const contentInput = document.getElementById('ug-content') as HTMLInputElement;
|
|
225
|
+
const outputEl = document.getElementById('ug-output') as HTMLElement;
|
|
226
|
+
const copyBtn = document.getElementById('ug-btn-copy') as HTMLButtonElement;
|
|
227
|
+
|
|
228
|
+
function updateUrl(): void {
|
|
229
|
+
const baseUrl = urlInput.value.trim();
|
|
230
|
+
if (!baseUrl) {
|
|
231
|
+
outputEl.textContent = t.errorInvalid;
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
try {
|
|
236
|
+
const url = new URL(baseUrl);
|
|
237
|
+
const params = new URLSearchParams(url.search);
|
|
238
|
+
|
|
239
|
+
if (sourceInput.value.trim()) params.set('utm_source', sourceInput.value.trim());
|
|
240
|
+
if (mediumInput.value.trim()) params.set('utm_medium', mediumInput.value.trim());
|
|
241
|
+
if (campaignInput.value.trim()) params.set('utm_campaign', campaignInput.value.trim());
|
|
242
|
+
if (termInput.value.trim()) params.set('utm_term', termInput.value.trim());
|
|
243
|
+
if (contentInput.value.trim()) params.set('utm_content', contentInput.value.trim());
|
|
244
|
+
|
|
245
|
+
url.search = params.toString();
|
|
246
|
+
outputEl.textContent = url.toString();
|
|
247
|
+
} catch {
|
|
248
|
+
outputEl.textContent = t.errorInvalidUrl;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
[urlInput, sourceInput, mediumInput, campaignInput, termInput, contentInput].forEach((input) => {
|
|
253
|
+
input.addEventListener('input', updateUrl);
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
copyBtn.addEventListener('click', () => {
|
|
257
|
+
const text = outputEl.textContent || '';
|
|
258
|
+
navigator.clipboard.writeText(text).then(() => {
|
|
259
|
+
const origText = copyBtn.textContent;
|
|
260
|
+
copyBtn.textContent = t.btnCopied;
|
|
261
|
+
copyBtn.classList.add('ug-copied');
|
|
262
|
+
setTimeout(() => {
|
|
263
|
+
copyBtn.textContent = origText;
|
|
264
|
+
copyBtn.classList.remove('ug-copied');
|
|
265
|
+
}, 2000);
|
|
266
|
+
});
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
updateUrl();
|
|
270
|
+
</script>
|