@jjlmoya/utils-cooking 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 +60 -0
- package/src/category/i18n/en.ts +24 -0
- package/src/category/i18n/es.ts +208 -0
- package/src/category/i18n/fr.ts +24 -0
- package/src/category/index.ts +37 -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 +32 -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/i18n-titles.test.ts +66 -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/seo_length.test.ts +22 -0
- package/src/tests/tool_validation.test.ts +17 -0
- package/src/tool/american-kitchen-converter/AmericanKitchenEngine.ts +259 -0
- package/src/tool/american-kitchen-converter/bibliography.astro +6 -0
- package/src/tool/american-kitchen-converter/component.astro +838 -0
- package/src/tool/american-kitchen-converter/i18n/en.ts +282 -0
- package/src/tool/american-kitchen-converter/i18n/es.ts +281 -0
- package/src/tool/american-kitchen-converter/i18n/fr.ts +292 -0
- package/src/tool/american-kitchen-converter/index.ts +24 -0
- package/src/tool/american-kitchen-converter/seo.astro +8 -0
- package/src/tool/banana-ripeness/BananaCare.css +587 -0
- package/src/tool/banana-ripeness/BananaEngine.ts +79 -0
- package/src/tool/banana-ripeness/bibliography.astro +6 -0
- package/src/tool/banana-ripeness/component.astro +285 -0
- package/src/tool/banana-ripeness/i18n/en.ts +177 -0
- package/src/tool/banana-ripeness/i18n/es.ts +177 -0
- package/src/tool/banana-ripeness/i18n/fr.ts +177 -0
- package/src/tool/banana-ripeness/index.ts +24 -0
- package/src/tool/banana-ripeness/seo.astro +8 -0
- package/src/tool/brine/bibliography.astro +6 -0
- package/src/tool/brine/component.astro +884 -0
- package/src/tool/brine/i18n/en.ts +221 -0
- package/src/tool/brine/i18n/es.ts +222 -0
- package/src/tool/brine/i18n/fr.ts +221 -0
- package/src/tool/brine/index.ts +26 -0
- package/src/tool/brine/seo.astro +8 -0
- package/src/tool/cookware-guide/CookwareGuide.css +487 -0
- package/src/tool/cookware-guide/bibliography.astro +6 -0
- package/src/tool/cookware-guide/component.astro +164 -0
- package/src/tool/cookware-guide/i18n/en.ts +163 -0
- package/src/tool/cookware-guide/i18n/es.ts +163 -0
- package/src/tool/cookware-guide/i18n/fr.ts +164 -0
- package/src/tool/cookware-guide/index.ts +24 -0
- package/src/tool/cookware-guide/init.ts +174 -0
- package/src/tool/cookware-guide/seo.astro +8 -0
- package/src/tool/egg-timer/EggTimer.css +503 -0
- package/src/tool/egg-timer/bibliography.astro +14 -0
- package/src/tool/egg-timer/component.astro +281 -0
- package/src/tool/egg-timer/i18n/en.ts +230 -0
- package/src/tool/egg-timer/i18n/es.ts +222 -0
- package/src/tool/egg-timer/i18n/fr.ts +121 -0
- package/src/tool/egg-timer/index.ts +27 -0
- package/src/tool/egg-timer/seo.astro +39 -0
- package/src/tool/ingredient-rescaler/IngredientRescaler.css +308 -0
- package/src/tool/ingredient-rescaler/bibliography.astro +6 -0
- package/src/tool/ingredient-rescaler/component.astro +107 -0
- package/src/tool/ingredient-rescaler/i18n/en.ts +265 -0
- package/src/tool/ingredient-rescaler/i18n/es.ts +268 -0
- package/src/tool/ingredient-rescaler/i18n/fr.ts +207 -0
- package/src/tool/ingredient-rescaler/index.ts +24 -0
- package/src/tool/ingredient-rescaler/init.ts +200 -0
- package/src/tool/ingredient-rescaler/seo.astro +8 -0
- package/src/tool/kitchen-timer/KitchenTimer.css +325 -0
- package/src/tool/kitchen-timer/bibliography.astro +6 -0
- package/src/tool/kitchen-timer/component.astro +341 -0
- package/src/tool/kitchen-timer/i18n/en.ts +154 -0
- package/src/tool/kitchen-timer/i18n/es.ts +154 -0
- package/src/tool/kitchen-timer/i18n/fr.ts +154 -0
- package/src/tool/kitchen-timer/index.ts +26 -0
- package/src/tool/kitchen-timer/init.ts +55 -0
- package/src/tool/kitchen-timer/lib/AudioHelper.ts +27 -0
- package/src/tool/kitchen-timer/lib/DockManager.ts +97 -0
- package/src/tool/kitchen-timer/lib/KitchenTimer.ts +264 -0
- package/src/tool/kitchen-timer/seo.astro +8 -0
- package/src/tool/meringue-peak/MeringueCalculator.css +298 -0
- package/src/tool/meringue-peak/bibliography.astro +6 -0
- package/src/tool/meringue-peak/component.astro +169 -0
- package/src/tool/meringue-peak/i18n/en.ts +257 -0
- package/src/tool/meringue-peak/i18n/es.ts +234 -0
- package/src/tool/meringue-peak/i18n/fr.ts +234 -0
- package/src/tool/meringue-peak/index.ts +24 -0
- package/src/tool/meringue-peak/seo.astro +8 -0
- package/src/tool/mold-scaler/MoldScaler.css +406 -0
- package/src/tool/mold-scaler/bibliography.astro +6 -0
- package/src/tool/mold-scaler/component.astro +126 -0
- package/src/tool/mold-scaler/i18n/en.ts +268 -0
- package/src/tool/mold-scaler/i18n/es.ts +269 -0
- package/src/tool/mold-scaler/i18n/fr.ts +276 -0
- package/src/tool/mold-scaler/index.ts +26 -0
- package/src/tool/mold-scaler/init.ts +264 -0
- package/src/tool/mold-scaler/seo.astro +8 -0
- package/src/tool/pizza/Pizza.css +569 -0
- package/src/tool/pizza/bibliography.astro +6 -0
- package/src/tool/pizza/calculator.ts +143 -0
- package/src/tool/pizza/component.astro +237 -0
- package/src/tool/pizza/i18n/en.ts +288 -0
- package/src/tool/pizza/i18n/es.ts +289 -0
- package/src/tool/pizza/i18n/fr.ts +288 -0
- package/src/tool/pizza/index.ts +27 -0
- package/src/tool/pizza/seo.astro +8 -0
- package/src/tool/roux-guide/RouxGuide.css +483 -0
- package/src/tool/roux-guide/bibliography.astro +6 -0
- package/src/tool/roux-guide/component.astro +194 -0
- package/src/tool/roux-guide/i18n/en.ts +233 -0
- package/src/tool/roux-guide/i18n/es.ts +225 -0
- package/src/tool/roux-guide/i18n/fr.ts +225 -0
- package/src/tool/roux-guide/index.ts +24 -0
- package/src/tool/roux-guide/init.ts +187 -0
- package/src/tool/roux-guide/seo.astro +8 -0
- package/src/tool/sourdough-calculator/SourdoughCalculator.css +369 -0
- package/src/tool/sourdough-calculator/bibliography.astro +6 -0
- package/src/tool/sourdough-calculator/component.astro +198 -0
- package/src/tool/sourdough-calculator/i18n/en.ts +242 -0
- package/src/tool/sourdough-calculator/i18n/es.ts +243 -0
- package/src/tool/sourdough-calculator/i18n/fr.ts +248 -0
- package/src/tool/sourdough-calculator/index.ts +24 -0
- package/src/tool/sourdough-calculator/init.ts +131 -0
- package/src/tool/sourdough-calculator/seo.astro +8 -0
- package/src/tools.ts +29 -0
- package/src/types.ts +73 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import * as DATA from '../data';
|
|
3
|
+
|
|
4
|
+
const ENTRIES = [
|
|
5
|
+
{ id: 'cookingCategory', i18n: DATA.cookingCategory.i18n },
|
|
6
|
+
];
|
|
7
|
+
|
|
8
|
+
describe('SEO Content Length Validation', () => {
|
|
9
|
+
ENTRIES.forEach((entry) => {
|
|
10
|
+
describe(`Tool: ${entry.id}`, () => {
|
|
11
|
+
Object.keys(entry.i18n).forEach((locale) => {
|
|
12
|
+
it(`${locale}: SEO section should exist`, async () => {
|
|
13
|
+
const loader = (entry.i18n as Record<string, () => Promise<{ seo?: unknown[] }>>)[locale];
|
|
14
|
+
const content = await loader();
|
|
15
|
+
if (!content.seo) return;
|
|
16
|
+
expect(Array.isArray(content.seo)).toBe(true);
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { ALL_TOOLS } from '../tools';
|
|
3
|
+
import { cookingCategory } from '../data';
|
|
4
|
+
|
|
5
|
+
describe('Tool Validation Suite', () => {
|
|
6
|
+
describe('Library Registration', () => {
|
|
7
|
+
it('should have 12 tools in ALL_TOOLS', () => {
|
|
8
|
+
expect(ALL_TOOLS.length).toBe(12);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it('cookingCategory should be defined', () => {
|
|
12
|
+
expect(cookingCategory).toBeDefined();
|
|
13
|
+
expect(cookingCategory.i18n).toBeDefined();
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
export class AmericanKitchenEngine {
|
|
2
|
+
static initTabs() {
|
|
3
|
+
const tabs = document.querySelectorAll(".akc-tab-btn");
|
|
4
|
+
const contents = document.querySelectorAll(".akc-tab-content");
|
|
5
|
+
tabs.forEach((tab) => {
|
|
6
|
+
tab.addEventListener("click", () => {
|
|
7
|
+
const targetId = tab.getAttribute("data-tab");
|
|
8
|
+
if (!targetId) return;
|
|
9
|
+
tabs.forEach((t) => t.classList.remove("active"));
|
|
10
|
+
tab.classList.add("active");
|
|
11
|
+
contents.forEach((c) => {
|
|
12
|
+
if (c.id === `content-${targetId}`) {
|
|
13
|
+
c.classList.add("active");
|
|
14
|
+
} else {
|
|
15
|
+
c.classList.remove("active");
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
private static createCupUpdater(
|
|
22
|
+
cupInput: HTMLInputElement,
|
|
23
|
+
cupResult: HTMLElement,
|
|
24
|
+
density: { value: number }
|
|
25
|
+
) {
|
|
26
|
+
return () => {
|
|
27
|
+
let val = parseFloat(cupInput.value);
|
|
28
|
+
if (isNaN(val) || val < 0) val = 0;
|
|
29
|
+
cupResult.textContent = Math.round(val * density.value).toString();
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
private static setupCupItems(
|
|
33
|
+
cupItems: NodeListOf<Element>,
|
|
34
|
+
density: { value: number },
|
|
35
|
+
updateFn: () => void
|
|
36
|
+
) {
|
|
37
|
+
cupItems.forEach((item) => {
|
|
38
|
+
item.addEventListener("click", () => {
|
|
39
|
+
cupItems.forEach((i) => i.classList.remove("active"));
|
|
40
|
+
item.classList.add("active");
|
|
41
|
+
const densityVal = item.getAttribute("data-density");
|
|
42
|
+
if (densityVal) {
|
|
43
|
+
density.value = parseFloat(densityVal);
|
|
44
|
+
updateFn();
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
private static setupCupButtons(
|
|
50
|
+
cupInput: HTMLInputElement | null,
|
|
51
|
+
cupPlus: HTMLElement | null,
|
|
52
|
+
cupMinus: HTMLElement | null,
|
|
53
|
+
updateFn: () => void
|
|
54
|
+
) {
|
|
55
|
+
if (cupPlus && cupInput) {
|
|
56
|
+
cupPlus.addEventListener("click", () => {
|
|
57
|
+
cupInput.value = (parseFloat(cupInput.value) + 0.25).toString();
|
|
58
|
+
updateFn();
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (cupMinus && cupInput) {
|
|
63
|
+
cupMinus.addEventListener("click", () => {
|
|
64
|
+
const newVal = parseFloat(cupInput.value) - 0.25;
|
|
65
|
+
if (newVal >= 0) {
|
|
66
|
+
cupInput.value = newVal.toString();
|
|
67
|
+
updateFn();
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
private static setupCupQuicks(
|
|
73
|
+
cupQuicks: NodeListOf<Element>,
|
|
74
|
+
cupInput: HTMLInputElement | null,
|
|
75
|
+
updateFn: () => void
|
|
76
|
+
) {
|
|
77
|
+
cupQuicks.forEach((btn) => {
|
|
78
|
+
btn.addEventListener("click", () => {
|
|
79
|
+
const val = btn.getAttribute("data-val");
|
|
80
|
+
if (val && cupInput) {
|
|
81
|
+
cupInput.value = val;
|
|
82
|
+
updateFn();
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
static initCups() {
|
|
88
|
+
const cupInput = document.getElementById("cup-input") as HTMLInputElement | null;
|
|
89
|
+
const cupResult = document.getElementById("cup-result");
|
|
90
|
+
if (!cupInput || !cupResult) return;
|
|
91
|
+
|
|
92
|
+
const density = { value: 120 };
|
|
93
|
+
const updateCups = this.createCupUpdater(cupInput, cupResult, density);
|
|
94
|
+
|
|
95
|
+
cupInput.addEventListener("input", updateCups);
|
|
96
|
+
this.setupCupItems(document.querySelectorAll(".cup-item"), density, updateCups);
|
|
97
|
+
this.setupCupButtons(
|
|
98
|
+
cupInput,
|
|
99
|
+
document.getElementById("cup-plus"),
|
|
100
|
+
document.getElementById("cup-minus"),
|
|
101
|
+
updateCups
|
|
102
|
+
);
|
|
103
|
+
this.setupCupQuicks(document.querySelectorAll(".cup-quick"), cupInput, updateCups);
|
|
104
|
+
}
|
|
105
|
+
private static createSpoonUpdater(
|
|
106
|
+
spoonInput: HTMLInputElement,
|
|
107
|
+
spoonResult: HTMLElement,
|
|
108
|
+
state: { density: number; multiplier: number }
|
|
109
|
+
) {
|
|
110
|
+
return () => {
|
|
111
|
+
let val = parseFloat(spoonInput.value);
|
|
112
|
+
if (isNaN(val) || val < 0) val = 0;
|
|
113
|
+
const result =
|
|
114
|
+
state.multiplier === 0.3333
|
|
115
|
+
? ((val * state.density) / 3).toFixed(1)
|
|
116
|
+
: (val * state.density).toFixed(1);
|
|
117
|
+
spoonResult.textContent = result.replace(".0", "");
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
private static setupSpoonItems(
|
|
121
|
+
spoonItems: NodeListOf<Element>,
|
|
122
|
+
state: { density: number; multiplier: number },
|
|
123
|
+
updateFn: () => void
|
|
124
|
+
) {
|
|
125
|
+
spoonItems.forEach((item) => {
|
|
126
|
+
item.addEventListener("click", () => {
|
|
127
|
+
spoonItems.forEach((i) => i.classList.remove("active"));
|
|
128
|
+
item.classList.add("active");
|
|
129
|
+
const density = item.getAttribute("data-density");
|
|
130
|
+
if (density) {
|
|
131
|
+
state.density = parseFloat(density);
|
|
132
|
+
updateFn();
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
private static setupSpoonToggles(
|
|
138
|
+
spoonToggles: NodeListOf<Element>,
|
|
139
|
+
state: { density: number; multiplier: number },
|
|
140
|
+
updateFn: () => void
|
|
141
|
+
) {
|
|
142
|
+
spoonToggles.forEach((toggle) => {
|
|
143
|
+
toggle.addEventListener("click", () => {
|
|
144
|
+
spoonToggles.forEach((t) => t.classList.remove("active"));
|
|
145
|
+
toggle.classList.add("active");
|
|
146
|
+
const mult = toggle.getAttribute("data-multiplier");
|
|
147
|
+
if (mult) {
|
|
148
|
+
state.multiplier = parseFloat(mult);
|
|
149
|
+
updateFn();
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
private static setupSpoonButtons(
|
|
155
|
+
spoonInput: HTMLInputElement | null,
|
|
156
|
+
spoonPlus: HTMLElement | null,
|
|
157
|
+
spoonMinus: HTMLElement | null,
|
|
158
|
+
updateFn: () => void
|
|
159
|
+
) {
|
|
160
|
+
if (spoonPlus && spoonInput) {
|
|
161
|
+
spoonPlus.addEventListener("click", () => {
|
|
162
|
+
spoonInput.value = (parseFloat(spoonInput.value) + 0.25).toString();
|
|
163
|
+
updateFn();
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (spoonMinus && spoonInput) {
|
|
168
|
+
spoonMinus.addEventListener("click", () => {
|
|
169
|
+
const newVal = parseFloat(spoonInput.value) - 0.25;
|
|
170
|
+
if (newVal >= 0) {
|
|
171
|
+
spoonInput.value = newVal.toString();
|
|
172
|
+
updateFn();
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
private static setupSpoonQuicks(
|
|
178
|
+
spoonQuicks: NodeListOf<Element>,
|
|
179
|
+
spoonInput: HTMLInputElement | null,
|
|
180
|
+
updateFn: () => void
|
|
181
|
+
) {
|
|
182
|
+
spoonQuicks.forEach((btn) => {
|
|
183
|
+
btn.addEventListener("click", () => {
|
|
184
|
+
const val = btn.getAttribute("data-val");
|
|
185
|
+
if (val && spoonInput) {
|
|
186
|
+
spoonInput.value = val;
|
|
187
|
+
updateFn();
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
static initSpoons() {
|
|
193
|
+
const spoonInput = document.getElementById("spoon-input") as HTMLInputElement | null;
|
|
194
|
+
const spoonResult = document.getElementById("spoon-result");
|
|
195
|
+
if (!spoonInput || !spoonResult) return;
|
|
196
|
+
|
|
197
|
+
const state = { density: 15, multiplier: 1 };
|
|
198
|
+
const updateSpoons = this.createSpoonUpdater(spoonInput, spoonResult, state);
|
|
199
|
+
|
|
200
|
+
spoonInput.addEventListener("input", updateSpoons);
|
|
201
|
+
this.setupSpoonItems(document.querySelectorAll(".spoon-item"), state, updateSpoons);
|
|
202
|
+
this.setupSpoonToggles(document.querySelectorAll(".spoon-toggle"), state, updateSpoons);
|
|
203
|
+
this.setupSpoonButtons(
|
|
204
|
+
spoonInput,
|
|
205
|
+
document.getElementById("spoon-plus"),
|
|
206
|
+
document.getElementById("spoon-minus"),
|
|
207
|
+
updateSpoons
|
|
208
|
+
);
|
|
209
|
+
this.setupSpoonQuicks(document.querySelectorAll(".spoon-quick"), spoonInput, updateSpoons);
|
|
210
|
+
}
|
|
211
|
+
private static setupTempConversion(
|
|
212
|
+
tempF: HTMLInputElement,
|
|
213
|
+
tempC: HTMLInputElement
|
|
214
|
+
) {
|
|
215
|
+
const updateF = () => {
|
|
216
|
+
const c = parseFloat(tempC.value);
|
|
217
|
+
if (isNaN(c)) {
|
|
218
|
+
tempF.value = "";
|
|
219
|
+
} else {
|
|
220
|
+
tempF.value = Math.round((c * 9) / 5 + 32).toString();
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
const updateC = () => {
|
|
225
|
+
const f = parseFloat(tempF.value);
|
|
226
|
+
if (isNaN(f)) {
|
|
227
|
+
tempC.value = "";
|
|
228
|
+
} else {
|
|
229
|
+
tempC.value = Math.round(((f - 32) * 5) / 9).toString();
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
tempF.addEventListener("input", updateC);
|
|
234
|
+
tempC.addEventListener("input", updateF);
|
|
235
|
+
}
|
|
236
|
+
private static setupTempPresets(
|
|
237
|
+
tempF: HTMLInputElement,
|
|
238
|
+
tempC: HTMLInputElement
|
|
239
|
+
) {
|
|
240
|
+
const tempPresets = document.querySelectorAll(".temp-preset");
|
|
241
|
+
tempPresets.forEach((preset) => {
|
|
242
|
+
preset.addEventListener("click", () => {
|
|
243
|
+
const f = preset.getAttribute("data-f");
|
|
244
|
+
if (f) {
|
|
245
|
+
tempF.value = f;
|
|
246
|
+
const c = Math.round(((parseFloat(f) - 32) * 5) / 9);
|
|
247
|
+
tempC.value = c.toString();
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
static initTemperatures() {
|
|
253
|
+
const tempF = document.getElementById("temp-f-input") as HTMLInputElement | null;
|
|
254
|
+
const tempC = document.getElementById("temp-c-input") as HTMLInputElement | null;
|
|
255
|
+
if (!tempF || !tempC) return;
|
|
256
|
+
|
|
257
|
+
this.setupTempConversion(tempF, tempC);
|
|
258
|
+
this.setupTempPresets(tempF, tempC);
|
|
259
|
+
}}
|