@jjlmoya/utils-tools 1.1.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 +63 -0
- package/src/category/i18n/en.ts +172 -0
- package/src/category/i18n/es.ts +172 -0
- package/src/category/i18n/fr.ts +172 -0
- package/src/category/index.ts +23 -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 +90 -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/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 +23 -0
- package/src/tests/title_quality.test.ts +56 -0
- package/src/tests/tool_validation.test.ts +17 -0
- package/src/tool/date-diff-calculator/bibliography.astro +14 -0
- package/src/tool/date-diff-calculator/component.astro +370 -0
- package/src/tool/date-diff-calculator/i18n/en.ts +132 -0
- package/src/tool/date-diff-calculator/i18n/es.ts +132 -0
- package/src/tool/date-diff-calculator/i18n/fr.ts +132 -0
- package/src/tool/date-diff-calculator/index.ts +22 -0
- package/src/tool/date-diff-calculator/seo.astro +14 -0
- package/src/tool/date-diff-calculator/ui.ts +17 -0
- package/src/tool/drive-direct-link/bibliography.astro +14 -0
- package/src/tool/drive-direct-link/component.astro +280 -0
- package/src/tool/drive-direct-link/i18n/en.ts +118 -0
- package/src/tool/drive-direct-link/i18n/es.ts +118 -0
- package/src/tool/drive-direct-link/i18n/fr.ts +118 -0
- package/src/tool/drive-direct-link/index.ts +22 -0
- package/src/tool/drive-direct-link/seo.astro +14 -0
- package/src/tool/drive-direct-link/ui.ts +10 -0
- package/src/tool/email-list-cleaner/bibliography.astro +14 -0
- package/src/tool/email-list-cleaner/component.astro +375 -0
- package/src/tool/email-list-cleaner/i18n/en.ts +140 -0
- package/src/tool/email-list-cleaner/i18n/es.ts +140 -0
- package/src/tool/email-list-cleaner/i18n/fr.ts +140 -0
- package/src/tool/email-list-cleaner/index.ts +22 -0
- package/src/tool/email-list-cleaner/seo.astro +14 -0
- package/src/tool/email-list-cleaner/ui.ts +15 -0
- package/src/tool/env-badge-spain/bibliography.astro +14 -0
- package/src/tool/env-badge-spain/component.astro +303 -0
- package/src/tool/env-badge-spain/components/BadgeForm.astro +243 -0
- package/src/tool/env-badge-spain/components/BadgeResult.astro +151 -0
- package/src/tool/env-badge-spain/i18n/en.ts +153 -0
- package/src/tool/env-badge-spain/i18n/es.ts +153 -0
- package/src/tool/env-badge-spain/i18n/fr.ts +153 -0
- package/src/tool/env-badge-spain/index.ts +22 -0
- package/src/tool/env-badge-spain/seo.astro +14 -0
- package/src/tool/env-badge-spain/ui.ts +53 -0
- package/src/tool/morse-beacon/bibliography.astro +14 -0
- package/src/tool/morse-beacon/component.astro +534 -0
- package/src/tool/morse-beacon/i18n/en.ts +157 -0
- package/src/tool/morse-beacon/i18n/es.ts +157 -0
- package/src/tool/morse-beacon/i18n/fr.ts +157 -0
- package/src/tool/morse-beacon/index.ts +22 -0
- package/src/tool/morse-beacon/logic/MorseEngine.ts +124 -0
- package/src/tool/morse-beacon/seo.astro +14 -0
- package/src/tool/morse-beacon/ui.ts +18 -0
- package/src/tool/password-generator/bibliography.astro +14 -0
- package/src/tool/password-generator/component.astro +259 -0
- package/src/tool/password-generator/components/Config.astro +227 -0
- package/src/tool/password-generator/components/Display.astro +147 -0
- package/src/tool/password-generator/components/Strength.astro +70 -0
- package/src/tool/password-generator/i18n/en.ts +166 -0
- package/src/tool/password-generator/i18n/es.ts +166 -0
- package/src/tool/password-generator/i18n/fr.ts +166 -0
- package/src/tool/password-generator/index.ts +22 -0
- package/src/tool/password-generator/seo.astro +14 -0
- package/src/tool/password-generator/ui.ts +16 -0
- package/src/tool/routes/bibliography.astro +14 -0
- package/src/tool/routes/component.astro +543 -0
- package/src/tool/routes/i18n/en.ts +157 -0
- package/src/tool/routes/i18n/es.ts +157 -0
- package/src/tool/routes/i18n/fr.ts +157 -0
- package/src/tool/routes/index.ts +22 -0
- package/src/tool/routes/logic/GeocodingService.ts +60 -0
- package/src/tool/routes/logic/RouteManager.ts +192 -0
- package/src/tool/routes/logic/RouteService.ts +66 -0
- package/src/tool/routes/seo.astro +14 -0
- package/src/tool/routes/ui.ts +16 -0
- package/src/tool/rule-of-three/bibliography.astro +14 -0
- package/src/tool/rule-of-three/component.astro +369 -0
- package/src/tool/rule-of-three/i18n/en.ts +171 -0
- package/src/tool/rule-of-three/i18n/es.ts +171 -0
- package/src/tool/rule-of-three/i18n/fr.ts +171 -0
- package/src/tool/rule-of-three/index.ts +22 -0
- package/src/tool/rule-of-three/seo.astro +14 -0
- package/src/tool/rule-of-three/ui.ts +13 -0
- package/src/tool/seo-content-optimizer/bibliography.astro +14 -0
- package/src/tool/seo-content-optimizer/component.astro +552 -0
- package/src/tool/seo-content-optimizer/i18n/en.ts +136 -0
- package/src/tool/seo-content-optimizer/i18n/es.ts +136 -0
- package/src/tool/seo-content-optimizer/i18n/fr.ts +136 -0
- package/src/tool/seo-content-optimizer/index.ts +22 -0
- package/src/tool/seo-content-optimizer/seo.astro +14 -0
- package/src/tool/seo-content-optimizer/ui.ts +29 -0
- package/src/tool/speed-reader/bibliography.astro +14 -0
- package/src/tool/speed-reader/component.astro +586 -0
- package/src/tool/speed-reader/i18n/en.ts +152 -0
- package/src/tool/speed-reader/i18n/es.ts +152 -0
- package/src/tool/speed-reader/i18n/fr.ts +152 -0
- package/src/tool/speed-reader/index.ts +22 -0
- package/src/tool/speed-reader/logic/RSVPEngine.ts +106 -0
- package/src/tool/speed-reader/seo.astro +14 -0
- package/src/tool/speed-reader/ui.ts +14 -0
- package/src/tool/text-pixel-calculator/bibliography.astro +14 -0
- package/src/tool/text-pixel-calculator/component.astro +315 -0
- package/src/tool/text-pixel-calculator/components/Editor.astro +240 -0
- package/src/tool/text-pixel-calculator/components/Preview.astro +155 -0
- package/src/tool/text-pixel-calculator/components/Stats.astro +87 -0
- package/src/tool/text-pixel-calculator/i18n/en.ts +133 -0
- package/src/tool/text-pixel-calculator/i18n/es.ts +133 -0
- package/src/tool/text-pixel-calculator/i18n/fr.ts +133 -0
- package/src/tool/text-pixel-calculator/index.ts +22 -0
- package/src/tool/text-pixel-calculator/seo.astro +14 -0
- package/src/tool/text-pixel-calculator/ui.ts +15 -0
- package/src/tool/whatsapp-link/bibliography.astro +14 -0
- package/src/tool/whatsapp-link/component.astro +455 -0
- package/src/tool/whatsapp-link/i18n/en.ts +128 -0
- package/src/tool/whatsapp-link/i18n/es.ts +128 -0
- package/src/tool/whatsapp-link/i18n/fr.ts +128 -0
- package/src/tool/whatsapp-link/index.ts +22 -0
- package/src/tool/whatsapp-link/seo.astro +14 -0
- package/src/tool/whatsapp-link/ui.ts +15 -0
- package/src/tools.ts +15 -0
- package/src/types.ts +72 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import type { ToolLocaleContent } from '../../../types';
|
|
2
|
+
import type { WithContext, FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
|
|
3
|
+
import type { DateDiffCalculatorUI } from '../ui';
|
|
4
|
+
|
|
5
|
+
const faqData = [
|
|
6
|
+
{
|
|
7
|
+
question: 'Cette calculatrice de différence de dates est-elle gratuite ?',
|
|
8
|
+
answer: 'Oui, c\'est un outil 100% gratuit accessible depuis n\'importe quel navigateur sans inscription ni téléchargement.',
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
question: 'Comment les années bissextiles sont-elles gérées ?',
|
|
12
|
+
answer: 'L\'outil utilise la chronologie standard JavaScript (UTC), qui gère automatiquement les années bissextiles et le nombre variable de jours dans chaque mois pour un résultat exact.',
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
question: 'Puis-je calculer la différence en heures et en minutes aussi ?',
|
|
16
|
+
answer: 'Oui. Les champs de saisie permettent de sélectionner l\'heure exacte. Le résultat donne une décomposition en jours, heures, minutes et secondes, ainsi que les totaux cumulés.',
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
question: 'Mes données de dates sont-elles envoyées à un serveur ?',
|
|
20
|
+
answer: 'Non. Tout le traitement est effectué localement dans votre navigateur. Nous ne stockons ni ne recevons aucune information sur les dates que vous calculez.',
|
|
21
|
+
},
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
const howToData = [
|
|
25
|
+
{ name: 'Sélectionner la date de début', text: "Saisissez la date et l'heure de départ ou cliquez sur 'Aujourd'hui' pour la fixer instantanément." },
|
|
26
|
+
{ name: 'Sélectionner la date de fin', text: "Définissez le moment cible. Utilisez 'Maintenant' pour mesurer le temps écoulé jusqu'à cet instant précis." },
|
|
27
|
+
{ name: 'Lire les résultats', text: 'Consultez la décomposition en jours, heures, minutes et secondes, ainsi que les totaux cumulés de semaines et d\'heures.' },
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
const faqSchema: WithContext<FAQPage> = {
|
|
31
|
+
'@context': 'https://schema.org',
|
|
32
|
+
'@type': 'FAQPage',
|
|
33
|
+
mainEntity: faqData.map((item) => ({
|
|
34
|
+
'@type': 'Question',
|
|
35
|
+
name: item.question,
|
|
36
|
+
acceptedAnswer: { '@type': 'Answer', text: item.answer },
|
|
37
|
+
})),
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const howToSchema: WithContext<HowTo> = {
|
|
41
|
+
'@context': 'https://schema.org',
|
|
42
|
+
'@type': 'HowTo',
|
|
43
|
+
name: 'Comment calculer la différence entre deux dates',
|
|
44
|
+
step: howToData.map((s) => ({ '@type': 'HowToStep', name: s.name, text: s.text })),
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const appSchema: WithContext<SoftwareApplication> = {
|
|
48
|
+
'@context': 'https://schema.org',
|
|
49
|
+
'@type': 'SoftwareApplication',
|
|
50
|
+
name: 'Calculateur de Différence de Dates',
|
|
51
|
+
applicationCategory: 'UtilitiesApplication',
|
|
52
|
+
operatingSystem: 'Web',
|
|
53
|
+
offers: { '@type': 'Offer', price: '0', priceCurrency: 'EUR' },
|
|
54
|
+
description: 'Calculez exactement combien de temps s\'est écoulé entre deux dates ou combien de temps il reste avant un événement.',
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const ui: DateDiffCalculatorUI = {
|
|
58
|
+
startLabel: 'Date de Début',
|
|
59
|
+
endLabel: 'Date de Fin',
|
|
60
|
+
nowBtn: 'Maintenant',
|
|
61
|
+
todayBtn: "Aujourd'hui",
|
|
62
|
+
tomorrowBtn: 'Demain',
|
|
63
|
+
daysLabel: 'Jours',
|
|
64
|
+
hoursLabel: 'Heures',
|
|
65
|
+
minsLabel: 'Min',
|
|
66
|
+
secsLabel: 'Sec',
|
|
67
|
+
weeksLabel: 'Semaines Totales',
|
|
68
|
+
totalHoursLabel: 'Heures Totales',
|
|
69
|
+
totalMinsLabel: 'Minutes Totales',
|
|
70
|
+
elapsed: 'Temps Écoulé',
|
|
71
|
+
past: 'Temps dans le Passé',
|
|
72
|
+
invalidDates: 'Dates invalides',
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export const content: ToolLocaleContent<DateDiffCalculatorUI> = {
|
|
76
|
+
slug: 'calculateur-difference-dates',
|
|
77
|
+
title: 'Calculateur de Différence de Dates',
|
|
78
|
+
description: 'Calculez exactement combien de temps s\'est écoulé entre deux dates ou combien il reste avant un événement. Outil gratuit avec résultats en jours, heures et minutes.',
|
|
79
|
+
ui,
|
|
80
|
+
faqTitle: 'Questions Fréquentes',
|
|
81
|
+
faq: faqData,
|
|
82
|
+
howTo: howToData,
|
|
83
|
+
bibliographyTitle: 'Références',
|
|
84
|
+
bibliography: [
|
|
85
|
+
{ name: 'Time and Date : Horloge Mondiale et Convertisseur de Fuseaux Horaires', url: 'https://www.timeanddate.com/worldclock/' },
|
|
86
|
+
{ name: 'Wikipedia : Histoire du Calendrier Grégorien', url: 'https://fr.wikipedia.org/wiki/Calendrier_gr%C3%A9gorien' },
|
|
87
|
+
{ name: 'Wikipedia : Matrice d\'Eisenhower et Gestion du Temps', url: 'https://fr.wikipedia.org/wiki/Matrice_d%27Eisenhower' },
|
|
88
|
+
],
|
|
89
|
+
schemas: [faqSchema, howToSchema, appSchema],
|
|
90
|
+
seo: [
|
|
91
|
+
{ type: 'title', level: 2, text: 'Calculateur de différence de dates en jours, heures et minutes' },
|
|
92
|
+
{
|
|
93
|
+
type: 'paragraph',
|
|
94
|
+
html: 'Calculer la <strong>différence entre deux dates</strong> est l\'une des tâches les plus courantes et sous-estimées, tant dans la vie quotidienne qu\'en milieu professionnel. Que vous planifiiez un lancement de produit, calculiez l\'âge exact d\'une personne ou mesuriez le temps restant avant un événement, disposer d\'un outil précis est essentiel pour une gestion du temps efficace.',
|
|
95
|
+
},
|
|
96
|
+
{ type: 'title', level: 3, text: 'À quoi sert la calculatrice de différence de dates ?' },
|
|
97
|
+
{
|
|
98
|
+
type: 'paragraph',
|
|
99
|
+
html: 'L\'utilité de cet outil s\'étend à de nombreux domaines. Dans le monde professionnel, les chefs de projet l\'utilisent pour définir les délais de livraison et suivre l\'avancement des jalons. Dans les contextes juridiques et administratifs, il est indispensable pour calculer les délais de prescription ou la durée des contrats.',
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
type: 'list',
|
|
103
|
+
items: [
|
|
104
|
+
'<strong>Planification d\'événements :</strong> Savoir combien de semaines et de jours restent avant un mariage, une conférence ou un voyage.',
|
|
105
|
+
'<strong>Gestion de projet :</strong> Calculer le temps écoulé depuis le début d\'une phase jusqu\'à son achèvement.',
|
|
106
|
+
'<strong>Finance :</strong> Déterminer le nombre de jours pour le calcul des intérêts ou les échéances de factures.',
|
|
107
|
+
'<strong>Ressources Humaines :</strong> Mesurer l\'ancienneté d\'un employé ou les jours de congé accumulés.',
|
|
108
|
+
'<strong>Santé :</strong> Suivre l\'évolution d\'un traitement médical ou le temps de récupération.',
|
|
109
|
+
],
|
|
110
|
+
},
|
|
111
|
+
{ type: 'title', level: 3, text: 'Perception du temps et précision numérique' },
|
|
112
|
+
{
|
|
113
|
+
type: 'paragraph',
|
|
114
|
+
html: 'Nous avons souvent tendance à arrondir le temps. Nous disons "dans un mois" alors qu\'il reste en réalité 27 jours et 14 heures. En utilisant une calculatrice numérique, on élimine la subjectivité et on obtient des données précises pour une prise de décision éclairée.',
|
|
115
|
+
},
|
|
116
|
+
{ type: 'title', level: 3, text: 'Jours calendaires et jours ouvrables' },
|
|
117
|
+
{
|
|
118
|
+
type: 'paragraph',
|
|
119
|
+
html: 'Cet outil calcule des <strong>jours calendaires</strong>, incluant les week-ends et jours fériés. Pour le calcul astronomique et civil général, le temps est mesuré en continu. Si vous estimez la durée d\'un projet, rappelez-vous que les jours affichés représentent le calendrier réel complet.',
|
|
120
|
+
},
|
|
121
|
+
{ type: 'title', level: 3, text: 'L\'impact du temps sur la productivité' },
|
|
122
|
+
{
|
|
123
|
+
type: 'paragraph',
|
|
124
|
+
html: 'La loi de Parkinson stipule que le travail s\'étend pour remplir le temps disponible. En visualisant exactement combien d\'heures et de minutes restent avant une échéance, les équipes ont tendance à optimiser leurs efforts.',
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
type: 'tip',
|
|
128
|
+
title: 'Dates futures et passées',
|
|
129
|
+
html: 'Vous pouvez utiliser la calculatrice pour des dates futures comme passées. Si la date de fin est antérieure à la date de début, le système détecte automatiquement qu\'il s\'agit de temps écoulé dans le passé, en conservant la précision des valeurs absolues.',
|
|
130
|
+
},
|
|
131
|
+
],
|
|
132
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ToolDefinition, ToolsToolEntry } from '../../types';
|
|
2
|
+
import type { DateDiffCalculatorUI } from './ui';
|
|
3
|
+
import DateDiffCalculatorComponent from './component.astro';
|
|
4
|
+
import DateDiffCalculatorSEO from './seo.astro';
|
|
5
|
+
import DateDiffCalculatorBibliography from './bibliography.astro';
|
|
6
|
+
|
|
7
|
+
export const dateDiffCalculator: ToolsToolEntry<DateDiffCalculatorUI> = {
|
|
8
|
+
id: 'date-diff-calculator',
|
|
9
|
+
icons: { bg: 'mdi:calendar-clock', fg: 'mdi:clock-fast' },
|
|
10
|
+
i18n: {
|
|
11
|
+
es: () => import('./i18n/es').then((m) => m.content),
|
|
12
|
+
en: () => import('./i18n/en').then((m) => m.content),
|
|
13
|
+
fr: () => import('./i18n/fr').then((m) => m.content),
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const DATE_DIFF_CALCULATOR_TOOL: ToolDefinition = {
|
|
18
|
+
entry: dateDiffCalculator,
|
|
19
|
+
Component: DateDiffCalculatorComponent,
|
|
20
|
+
SEOComponent: DateDiffCalculatorSEO,
|
|
21
|
+
BibliographyComponent: DateDiffCalculatorBibliography,
|
|
22
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { SEORenderer } from '@jjlmoya/utils-shared';
|
|
3
|
+
import { dateDiffCalculator } 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 dateDiffCalculator.i18n[locale]?.();
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
{content && <SEORenderer content={{ locale, sections: content.seo }} />}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface DateDiffCalculatorUI extends Record<string, string> {
|
|
2
|
+
startLabel: string;
|
|
3
|
+
endLabel: string;
|
|
4
|
+
nowBtn: string;
|
|
5
|
+
todayBtn: string;
|
|
6
|
+
tomorrowBtn: string;
|
|
7
|
+
daysLabel: string;
|
|
8
|
+
hoursLabel: string;
|
|
9
|
+
minsLabel: string;
|
|
10
|
+
secsLabel: string;
|
|
11
|
+
weeksLabel: string;
|
|
12
|
+
totalHoursLabel: string;
|
|
13
|
+
totalMinsLabel: string;
|
|
14
|
+
elapsed: string;
|
|
15
|
+
past: string;
|
|
16
|
+
invalidDates: string;
|
|
17
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { Bibliography as SharedBibliography } from '@jjlmoya/utils-shared';
|
|
3
|
+
import { driveDirectLink } 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 driveDirectLink.i18n[locale]?.();
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
{content && <SharedBibliography links={content.bibliography} />}
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { DriveDirectLinkUI } from './ui';
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
ui?: Record<string, unknown>;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const { ui } = Astro.props;
|
|
9
|
+
const t = (ui ?? {}) as DriveDirectLinkUI;
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
<div class="gdl-root" data-ui={JSON.stringify(t)}>
|
|
13
|
+
<div class="gdl-card">
|
|
14
|
+
<div class="gdl-field">
|
|
15
|
+
<label class="gdl-label" for="gdl-input">{t.inputLabel}</label>
|
|
16
|
+
<input
|
|
17
|
+
class="gdl-input"
|
|
18
|
+
type="text"
|
|
19
|
+
id="gdl-input"
|
|
20
|
+
placeholder={t.inputPlaceholder}
|
|
21
|
+
autocomplete="off"
|
|
22
|
+
spellcheck="false"
|
|
23
|
+
/>
|
|
24
|
+
</div>
|
|
25
|
+
|
|
26
|
+
<button class="gdl-btn-primary" id="gdl-generate" type="button">{t.generateBtn}</button>
|
|
27
|
+
|
|
28
|
+
<div class="gdl-result" id="gdl-result" style="display: none">
|
|
29
|
+
<label class="gdl-result-label">{t.resultLabel}</label>
|
|
30
|
+
<div class="gdl-output-row">
|
|
31
|
+
<input class="gdl-input gdl-output" type="text" id="gdl-output" readonly />
|
|
32
|
+
<button class="gdl-copy-btn" id="gdl-copy" type="button" title={t.copyTitle}>
|
|
33
|
+
<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor"><path d="M19 21H8V7h11m0-2H8a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h11a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2m-3-4H4a2 2 0 0 0-2 2v14h2V3h12V1Z"/></svg>
|
|
34
|
+
</button>
|
|
35
|
+
</div>
|
|
36
|
+
<a class="gdl-test-btn" id="gdl-test" href="#" target="_blank" rel="noopener noreferrer">{t.testBtn}</a>
|
|
37
|
+
</div>
|
|
38
|
+
|
|
39
|
+
<div class="gdl-error" id="gdl-error" style="display: none">{t.errorMsg}</div>
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
42
|
+
|
|
43
|
+
<style>
|
|
44
|
+
.gdl-root {
|
|
45
|
+
--gdl-blue: #2563eb;
|
|
46
|
+
--gdl-blue-light: #3b82f6;
|
|
47
|
+
--gdl-green: #10b981;
|
|
48
|
+
--gdl-green-bg: rgba(16, 185, 129, 0.08);
|
|
49
|
+
--gdl-green-border: rgba(16, 185, 129, 0.3);
|
|
50
|
+
--gdl-card-bg: #fff;
|
|
51
|
+
--gdl-card-border: #e2e8f0;
|
|
52
|
+
--gdl-field-bg: #f8fafc;
|
|
53
|
+
--gdl-field-border: #cbd5e1;
|
|
54
|
+
--gdl-text-main: #1e293b;
|
|
55
|
+
--gdl-text-label: #374151;
|
|
56
|
+
--gdl-error-bg: rgba(239, 68, 68, 0.1);
|
|
57
|
+
--gdl-error-border: rgba(239, 68, 68, 0.3);
|
|
58
|
+
--gdl-divider: rgba(0, 0, 0, 0.08);
|
|
59
|
+
|
|
60
|
+
width: 100%;
|
|
61
|
+
max-width: 600px;
|
|
62
|
+
margin: 0 auto;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
:global(.theme-dark) .gdl-root {
|
|
66
|
+
--gdl-card-bg: #1e293b;
|
|
67
|
+
--gdl-card-border: #334155;
|
|
68
|
+
--gdl-field-bg: #0f172a;
|
|
69
|
+
--gdl-field-border: #475569;
|
|
70
|
+
--gdl-text-main: #f1f5f9;
|
|
71
|
+
--gdl-text-label: #cbd5e1;
|
|
72
|
+
--gdl-divider: rgba(255, 255, 255, 0.08);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.gdl-card {
|
|
76
|
+
background: var(--gdl-card-bg);
|
|
77
|
+
border: 1px solid var(--gdl-card-border);
|
|
78
|
+
border-radius: 1.5rem;
|
|
79
|
+
padding: 2.5rem;
|
|
80
|
+
display: flex;
|
|
81
|
+
flex-direction: column;
|
|
82
|
+
gap: 1.5rem;
|
|
83
|
+
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.gdl-field {
|
|
87
|
+
display: flex;
|
|
88
|
+
flex-direction: column;
|
|
89
|
+
gap: 0.75rem;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.gdl-label {
|
|
93
|
+
font-size: 1rem;
|
|
94
|
+
font-weight: 500;
|
|
95
|
+
color: var(--gdl-text-label);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.gdl-input {
|
|
99
|
+
width: 100%;
|
|
100
|
+
padding: 1rem 1.25rem;
|
|
101
|
+
background: var(--gdl-field-bg);
|
|
102
|
+
border: 1px solid var(--gdl-field-border);
|
|
103
|
+
border-radius: 0.75rem;
|
|
104
|
+
color: var(--gdl-text-main);
|
|
105
|
+
font-size: 1rem;
|
|
106
|
+
outline: none;
|
|
107
|
+
transition: border-color 0.2s, box-shadow 0.2s;
|
|
108
|
+
box-sizing: border-box;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.gdl-input:focus {
|
|
112
|
+
border-color: var(--gdl-blue);
|
|
113
|
+
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.2);
|
|
114
|
+
background: var(--gdl-card-bg);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.gdl-btn-primary {
|
|
118
|
+
background: linear-gradient(135deg, var(--gdl-blue-light) 0%, var(--gdl-blue) 100%);
|
|
119
|
+
color: #fff;
|
|
120
|
+
border: none;
|
|
121
|
+
border-radius: 0.75rem;
|
|
122
|
+
padding: 1rem 1.5rem;
|
|
123
|
+
font-size: 1.0625rem;
|
|
124
|
+
font-weight: 600;
|
|
125
|
+
cursor: pointer;
|
|
126
|
+
transition: transform 0.2s, box-shadow 0.2s;
|
|
127
|
+
box-shadow: 0 4px 15px rgba(37, 99, 235, 0.4);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.gdl-btn-primary:hover {
|
|
131
|
+
transform: translateY(-2px);
|
|
132
|
+
box-shadow: 0 6px 20px rgba(37, 99, 235, 0.5);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.gdl-btn-primary:active {
|
|
136
|
+
transform: translateY(0);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.gdl-result {
|
|
140
|
+
flex-direction: column;
|
|
141
|
+
gap: 1rem;
|
|
142
|
+
padding-top: 1.5rem;
|
|
143
|
+
border-top: 1px solid var(--gdl-divider);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.gdl-result-label {
|
|
147
|
+
font-size: 0.9375rem;
|
|
148
|
+
font-weight: 600;
|
|
149
|
+
color: var(--gdl-green);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.gdl-output-row {
|
|
153
|
+
display: flex;
|
|
154
|
+
gap: 0.5rem;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.gdl-output {
|
|
158
|
+
color: var(--gdl-green);
|
|
159
|
+
background: var(--gdl-green-bg);
|
|
160
|
+
border-color: var(--gdl-green-border);
|
|
161
|
+
flex: 1;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.gdl-copy-btn {
|
|
165
|
+
display: flex;
|
|
166
|
+
align-items: center;
|
|
167
|
+
justify-content: center;
|
|
168
|
+
background: var(--gdl-green-bg);
|
|
169
|
+
border: 1px solid var(--gdl-green-border);
|
|
170
|
+
border-radius: 0.75rem;
|
|
171
|
+
color: var(--gdl-green);
|
|
172
|
+
width: 3rem;
|
|
173
|
+
min-width: 3rem;
|
|
174
|
+
cursor: pointer;
|
|
175
|
+
transition: background 0.2s;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
.gdl-copy-btn:hover {
|
|
179
|
+
background: rgba(16, 185, 129, 0.2);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.gdl-test-btn {
|
|
183
|
+
display: block;
|
|
184
|
+
text-align: center;
|
|
185
|
+
background: var(--gdl-green-bg);
|
|
186
|
+
color: var(--gdl-green);
|
|
187
|
+
border: 1px solid var(--gdl-green-border);
|
|
188
|
+
border-radius: 0.75rem;
|
|
189
|
+
padding: 0.75rem 1rem;
|
|
190
|
+
font-size: 1rem;
|
|
191
|
+
font-weight: 600;
|
|
192
|
+
text-decoration: none;
|
|
193
|
+
transition: background 0.2s, border-color 0.2s;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
.gdl-test-btn:hover {
|
|
197
|
+
background: rgba(16, 185, 129, 0.2);
|
|
198
|
+
border-color: rgba(16, 185, 129, 0.5);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
.gdl-error {
|
|
202
|
+
color: #ef4444;
|
|
203
|
+
background: var(--gdl-error-bg);
|
|
204
|
+
border: 1px solid var(--gdl-error-border);
|
|
205
|
+
border-radius: 0.75rem;
|
|
206
|
+
padding: 1rem;
|
|
207
|
+
font-size: 0.9375rem;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
@media (max-width: 480px) {
|
|
211
|
+
.gdl-card {
|
|
212
|
+
padding: 1.5rem;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
</style>
|
|
216
|
+
|
|
217
|
+
<script>
|
|
218
|
+
import type { DriveDirectLinkUI } from './ui';
|
|
219
|
+
|
|
220
|
+
const root = document.querySelector('.gdl-root') as HTMLElement;
|
|
221
|
+
const t = JSON.parse(root?.dataset.ui ?? '{}') as DriveDirectLinkUI;
|
|
222
|
+
|
|
223
|
+
const input = document.getElementById('gdl-input') as HTMLInputElement;
|
|
224
|
+
const generateBtn = document.getElementById('gdl-generate') as HTMLButtonElement;
|
|
225
|
+
const result = document.getElementById('gdl-result') as HTMLDivElement;
|
|
226
|
+
const output = document.getElementById('gdl-output') as HTMLInputElement;
|
|
227
|
+
const copyBtn = document.getElementById('gdl-copy') as HTMLButtonElement;
|
|
228
|
+
const testBtn = document.getElementById('gdl-test') as HTMLAnchorElement;
|
|
229
|
+
const errorEl = document.getElementById('gdl-error') as HTMLDivElement;
|
|
230
|
+
|
|
231
|
+
function extractDriveId(url: string): string | null {
|
|
232
|
+
try {
|
|
233
|
+
const parsed = new URL(url);
|
|
234
|
+
const segments = parsed.pathname.split('/');
|
|
235
|
+
const dIdx = segments.indexOf('d');
|
|
236
|
+
if (dIdx !== -1 && dIdx + 1 < segments.length) return segments[dIdx + 1] ?? null;
|
|
237
|
+
return parsed.searchParams.get('id');
|
|
238
|
+
} catch {
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function generate(): void {
|
|
244
|
+
const url = input.value.trim();
|
|
245
|
+
if (!url) {
|
|
246
|
+
result.style.display = 'none';
|
|
247
|
+
errorEl.style.display = 'none';
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const id = extractDriveId(url);
|
|
252
|
+
if (id) {
|
|
253
|
+
const direct = `https://drive.google.com/uc?export=download&id=${id}`;
|
|
254
|
+
output.value = direct;
|
|
255
|
+
testBtn.href = direct;
|
|
256
|
+
result.style.display = 'flex';
|
|
257
|
+
errorEl.style.display = 'none';
|
|
258
|
+
} else {
|
|
259
|
+
result.style.display = 'none';
|
|
260
|
+
errorEl.style.display = 'block';
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
generateBtn.addEventListener('click', generate);
|
|
265
|
+
input.addEventListener('keydown', (e) => { if (e.key === 'Enter') generate(); });
|
|
266
|
+
|
|
267
|
+
copyBtn.addEventListener('click', async () => {
|
|
268
|
+
try {
|
|
269
|
+
await navigator.clipboard.writeText(output.value);
|
|
270
|
+
const svg = copyBtn.innerHTML;
|
|
271
|
+
copyBtn.textContent = t.copyDone;
|
|
272
|
+
copyBtn.style.minWidth = '4.5rem';
|
|
273
|
+
setTimeout(() => {
|
|
274
|
+
copyBtn.innerHTML = svg;
|
|
275
|
+
copyBtn.style.minWidth = '';
|
|
276
|
+
}, 1500);
|
|
277
|
+
} catch {
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
</script>
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import type { ToolLocaleContent } from '../../../types';
|
|
2
|
+
import type { WithContext, FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
|
|
3
|
+
import type { DriveDirectLinkUI } from '../ui';
|
|
4
|
+
|
|
5
|
+
const faqData = [
|
|
6
|
+
{
|
|
7
|
+
question: 'How do I use the Google Drive download converter?',
|
|
8
|
+
answer: 'Copy the full "Share" link from any file stored on Google Drive (it must have public permissions). Paste it into the tool\'s text box and click "Generate Direct Link". A new URL will appear that starts the download without opening Google\'s viewer.',
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
question: 'Can I generate a direct download link for a private file?',
|
|
12
|
+
answer: 'If the file is private or access-restricted, Google will ask you to sign in with an authorised account. For clean downloads without registration, the file permissions must be set to "Anyone with the link can view".',
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
question: 'Is it safe to convert my Drive links here?',
|
|
16
|
+
answer: 'Yes. The conversion happens 100% in your browser and is completely anonymous. The tool does not store your link or make any server calls; it only analyses the URL structure client-side to generate the download version.',
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
question: 'What happens if my Drive file is very large?',
|
|
20
|
+
answer: 'For files larger than approximately 100 MB, Google Drive does not allow an instant direct download. Instead, it shows a warning page about file size and antivirus scanning bypass. This is a fixed Google server policy that no external tool can circumvent.',
|
|
21
|
+
},
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
const howToData = [
|
|
25
|
+
{ name: 'Copy the share link', text: 'In Google Drive, right-click the file and select "Get link". Make sure the permissions allow access to anyone with the link.' },
|
|
26
|
+
{ name: 'Paste the link into the generator', text: 'Enter the full Drive URL in the text field of the tool and click the "Generate Direct Link" button.' },
|
|
27
|
+
{ name: 'Copy and use the generated link', text: 'Copy the new direct download link and use it in emails, websites, newsletters or anywhere you need the file to download immediately on click.' },
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
const faqSchema: WithContext<FAQPage> = {
|
|
31
|
+
'@context': 'https://schema.org',
|
|
32
|
+
'@type': 'FAQPage',
|
|
33
|
+
mainEntity: faqData.map((item) => ({
|
|
34
|
+
'@type': 'Question',
|
|
35
|
+
name: item.question,
|
|
36
|
+
acceptedAnswer: { '@type': 'Answer', text: item.answer },
|
|
37
|
+
})),
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const howToSchema: WithContext<HowTo> = {
|
|
41
|
+
'@context': 'https://schema.org',
|
|
42
|
+
'@type': 'HowTo',
|
|
43
|
+
name: 'How to generate a direct download link for Google Drive',
|
|
44
|
+
step: howToData.map((s) => ({ '@type': 'HowToStep', name: s.name, text: s.text })),
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const appSchema: WithContext<SoftwareApplication> = {
|
|
48
|
+
'@context': 'https://schema.org',
|
|
49
|
+
'@type': 'SoftwareApplication',
|
|
50
|
+
name: 'Google Drive Direct Download Link Generator',
|
|
51
|
+
applicationCategory: 'UtilitiesApplication',
|
|
52
|
+
operatingSystem: 'Web',
|
|
53
|
+
offers: { '@type': 'Offer', price: '0', priceCurrency: 'EUR' },
|
|
54
|
+
description: 'Convert any Google Drive share link into a direct download link with one click, bypassing the file preview page. Free tool.',
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const ui: DriveDirectLinkUI = {
|
|
58
|
+
inputLabel: 'Paste your Google Drive link here:',
|
|
59
|
+
inputPlaceholder: 'https://drive.google.com/file/d/...',
|
|
60
|
+
generateBtn: 'Generate Direct Link',
|
|
61
|
+
resultLabel: 'Direct Download Link:',
|
|
62
|
+
copyTitle: 'Copy to clipboard',
|
|
63
|
+
copyDone: 'Copied',
|
|
64
|
+
testBtn: 'Test Download',
|
|
65
|
+
errorMsg: 'The link you entered does not appear to be a valid Google Drive link.',
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export const content: ToolLocaleContent<DriveDirectLinkUI> = {
|
|
69
|
+
slug: 'google-drive-direct-download-link',
|
|
70
|
+
title: 'Google Drive Direct Download Link Generator',
|
|
71
|
+
description: 'Easily convert any Google Drive share link into a direct download link with one click, bypassing the file preview. Free tool.',
|
|
72
|
+
ui,
|
|
73
|
+
faqTitle: 'Frequently Asked Questions',
|
|
74
|
+
faq: faqData,
|
|
75
|
+
howTo: howToData,
|
|
76
|
+
bibliographyTitle: 'References',
|
|
77
|
+
bibliography: [
|
|
78
|
+
{ name: 'Download files from Google Drive (Google Workspace)', url: 'https://support.google.com/drive/answer/2423534' },
|
|
79
|
+
{ name: 'Google Drive API: Download Files', url: 'https://developers.google.com/drive/api/guides/manage-downloads' },
|
|
80
|
+
],
|
|
81
|
+
schemas: [faqSchema, howToSchema, appSchema],
|
|
82
|
+
seo: [
|
|
83
|
+
{ type: 'title', level: 2, text: 'Convert Google Drive share links to direct download links' },
|
|
84
|
+
{
|
|
85
|
+
type: 'paragraph',
|
|
86
|
+
html: 'If you share files frequently via Google Drive, you know how annoying the preview page can be. When your users click your link, they land on an intermediate screen that forces them to find the download button. Our <strong>Google Drive direct download link generator</strong> solves this by converting any share link into one that starts the download automatically.',
|
|
87
|
+
},
|
|
88
|
+
{ type: 'title', level: 3, text: 'Why use a direct download link?' },
|
|
89
|
+
{
|
|
90
|
+
type: 'paragraph',
|
|
91
|
+
html: 'The main benefit is a much better user experience. When sharing a PDF, image or software installer, users expect clicking the link to start the download immediately:',
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
type: 'list',
|
|
95
|
+
items: [
|
|
96
|
+
'<strong>Saves time:</strong> One-click download with no extra steps.',
|
|
97
|
+
'<strong>Professional look:</strong> Ideal for download buttons on websites, newsletters or client emails.',
|
|
98
|
+
'<strong>Fewer drop-offs:</strong> Less tech-savvy users can be confused by the Drive preview and not know how to download.',
|
|
99
|
+
'<strong>HTML-ready:</strong> Perfect for <code>href</code> or <code>src</code> attributes in your web pages.',
|
|
100
|
+
],
|
|
101
|
+
},
|
|
102
|
+
{ type: 'title', level: 3, text: 'How does the direct link generator work?' },
|
|
103
|
+
{
|
|
104
|
+
type: 'paragraph',
|
|
105
|
+
html: 'Everything happens 100% in your browser. Google Drive share links contain a unique file ID. The tool extracts that ID and builds a new URL using Google\'s native export parameter: <code>https://drive.google.com/uc?export=download&id=YOUR_ID</code>.',
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
type: 'tip',
|
|
109
|
+
title: 'Warning for large files',
|
|
110
|
+
html: 'For files over approximately 100 MB, Google Drive displays an antivirus scan warning page before the download starts. This is a fixed Google server policy that no external tool can bypass.',
|
|
111
|
+
},
|
|
112
|
+
{ type: 'title', level: 3, text: 'Privacy and security guaranteed' },
|
|
113
|
+
{
|
|
114
|
+
type: 'paragraph',
|
|
115
|
+
html: 'This tool fully respects your privacy. The entire link transformation is performed via client-side JavaScript on your own device. No link, file ID or personal information is ever sent to any external server.',
|
|
116
|
+
},
|
|
117
|
+
],
|
|
118
|
+
};
|