Skip to content

Commit 536f138

Browse files
committed
🚨[Hotfix] Activate released OCR category entry
1 parent b82e565 commit 536f138

3 files changed

Lines changed: 76 additions & 7 deletions

File tree

‎tests/browser/ocr-smoke.js‎

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,42 @@ import { createOcrService } from "../../tools/shared/ocr.js";
22

33
const output = document.querySelector("#result");
44
const requestsBefore = performance.getEntriesByType("resource").map((entry) => entry.name);
5+
6+
async function verifyCategoryAvailability() {
7+
const loadCategory = async (pathname) => {
8+
const response = await fetch(pathname);
9+
if (!response.ok) throw new Error(`${pathname} returned HTTP ${response.status}`);
10+
return new DOMParser().parseFromString(await response.text(), "text/html");
11+
};
12+
const findOcrCard = (documentObject) => documentObject
13+
.querySelector('[data-i18n="tools.imageToText"]')
14+
?.closest("a.category-tool");
15+
const cardPath = (card, categoryPath) => new URL(card.getAttribute("href"), new URL(categoryPath, location.origin)).pathname;
16+
17+
const [imageDocument, scanDocument] = await Promise.all([
18+
loadCategory("/tools/image/"),
19+
loadCategory("/tools/scan/"),
20+
]);
21+
const imageCard = findOcrCard(imageDocument);
22+
const scanCard = findOcrCard(scanDocument);
23+
if (!imageCard || !scanCard) throw new Error("Image to Text must be linked from both categories");
24+
if (!imageCard.querySelector(".status--available") || !scanCard.querySelector(".status--available")) {
25+
throw new Error("Image to Text must be available in both categories");
26+
}
27+
const imagePath = cardPath(imageCard, "/tools/image/");
28+
const scanPath = cardPath(scanCard, "/tools/scan/");
29+
if (imagePath !== "/tools/image/to-text/" || scanPath !== imagePath) {
30+
throw new Error(`Category routes differ: ${imagePath}, ${scanPath}`);
31+
}
32+
const routeResponse = await fetch(scanPath);
33+
if (!routeResponse.ok) throw new Error(`${scanPath} returned HTTP ${routeResponse.status}`);
34+
const disabledControl = scanDocument.querySelector('[data-i18n="categories.scan.documentTitle"]')?.closest("article.category-tool");
35+
if (!disabledControl?.querySelector('[data-i18n="tools.comingSoon"]')) {
36+
throw new Error("Document Scanner must remain disabled");
37+
}
38+
return { imagePath, scanPath, disabledControl: true };
39+
}
40+
541
const canvas = document.createElement("canvas");
642
canvas.width = 720;
743
canvas.height = 220;
@@ -15,12 +51,13 @@ context.fillText("HELLO", 120, 145);
1551
const image = await new Promise((resolve) => canvas.toBlob(resolve, "image/png"));
1652
const service = createOcrService();
1753
try {
54+
const categoryAvailability = await verifyCategoryAvailability();
1855
const result = await service.recognizeImage(image, { language: "eng" });
1956
const requests = performance.getEntriesByType("resource").map((entry) => entry.name).slice(requestsBefore.length);
2057
const externalRequests = requests.filter((value) => new URL(value).origin !== location.origin);
2158
if (!/HELLO/i.test(result.text)) throw new Error(`Unexpected OCR text: ${result.text}`);
2259
if (externalRequests.length) throw new Error(`External requests: ${externalRequests.join(", ")}`);
23-
window.__ocrSmokeResult = { ok: true, text: result.text.trim(), requests, externalRequests };
60+
window.__ocrSmokeResult = { ok: true, text: result.text.trim(), requests, externalRequests, categoryAvailability };
2461
output.textContent = `PASS: ${result.text.trim()}`;
2562
} catch (error) {
2663
window.__ocrSmokeResult = { ok: false, error: error.message, cause: error.cause?.message || null };

‎tests/category-availability.test.mjs‎

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@ function assertRoutesExist(categoryPage, routes) {
2626
}
2727
}
2828

29+
function linkedCard(list, titleKey) {
30+
return [...list.matchAll(/<a class="category-tool surface" href="([^"]+)">([\s\S]*?)<\/a>/g)]
31+
.find((match) => match[2].includes(`data-i18n="${titleKey}"`));
32+
}
33+
2934
const imageHtml = read("tools/image/index.html");
3035
const imageList = categoryList(imageHtml);
3136
const imageRoutes = ["./converter/", "./resize/", "./compress/", "./metadata/", "./to-text/"];
@@ -35,6 +40,29 @@ assert.equal((imageList.match(/status--available/g) || []).length, 5);
3540
assert.doesNotMatch(imageHtml, /<\/ul>\s*<li>/, "Image metadata card must remain inside the semantic list");
3641
assertRoutesExist("tools/image/index.html", imageRoutes);
3742

43+
const imageOcrCard = linkedCard(imageList, "tools.imageToText");
44+
assert.ok(imageOcrCard, "Image category must link Image to Text");
45+
assert.match(imageOcrCard[2], /status--available/);
46+
assert.doesNotMatch(imageOcrCard[2], /tools\.comingSoon|<article\b/);
47+
48+
const scanHtml = read("tools/scan/index.html");
49+
const scanList = categoryList(scanHtml);
50+
const scanOcrCard = linkedCard(scanList, "tools.imageToText");
51+
assert.ok(scanOcrCard, "Scan and OCR category must link Image to Text");
52+
assert.equal(scanOcrCard[1], "../image/to-text/");
53+
assert.match(scanOcrCard[2], /status--available/);
54+
assert.match(scanOcrCard[2], /data-i18n="categories\.image\.toText"/);
55+
assert.doesNotMatch(scanOcrCard[2], /tools\.comingSoon|<article\b|categories\.scan\.ocr/);
56+
assert.equal(
57+
path.resolve(root, "tools/image", imageOcrCard[1]),
58+
path.resolve(root, "tools/scan", scanOcrCard[1]),
59+
"Image and Scan/OCR entries must resolve to the same route",
60+
);
61+
assertRoutesExist("tools/scan/index.html", [scanOcrCard[1]]);
62+
assert.equal((scanList.match(/<article class="category-tool surface">/g) || []).length, 1);
63+
assert.equal((scanList.match(/tools\.comingSoon/g) || []).length, 1);
64+
assert.match(scanList, /data-i18n="categories\.scan\.documentTitle"/);
65+
3866
const privacyHtml = read("tools/privacy/index.html");
3967
const privacyList = categoryList(privacyHtml);
4068
const privacyRoutes = ["../image/metadata/", "../pdf/metadata/"];
@@ -50,13 +78,13 @@ const pdfList = categoryList(read("tools/pdf/index.html"));
5078
assert.equal(linkedRoutes(pdfList).length, 6, "Every PDF production card must remain linked");
5179
assert.equal((pdfList.match(/status--available/g) || []).length, 6);
5280

53-
for (const category of ["pdf", "image"]) {
81+
for (const category of ["pdf", "image", "scan"]) {
5482
const html = read(`tools/${category}/index.html`);
5583
assert.match(html, /data-i18n="categories\.localNote">Available tools process file contents locally in browser memory\./);
5684
assert.doesNotMatch(html, /Planned items are clearly marked/);
5785
}
5886

59-
for (const category of ["scan", "media"]) {
87+
for (const category of ["media"]) {
6088
const plannedList = categoryList(read(`tools/${category}/index.html`));
6189
assert.equal(linkedRoutes(plannedList).length, 0, `${category} must remain planned`);
6290
assert.equal((plannedList.match(/<article class="category-tool surface">/g) || []).length, 2);
@@ -68,6 +96,10 @@ for (const [language, catalog] of Object.entries(translations)) {
6896
assert.doesNotMatch(catalog.categories.localNote, /planned|geplant|prévu|planificad|準備中|준비 중/i, `${language} production category note mentions planned work`);
6997
assert.equal(typeof catalog.privacyHub.imageDescription, "string", `${language} image scope is missing`);
7098
assert.equal(typeof catalog.privacyHub.pdfDescription, "string", `${language} PDF scope is missing`);
99+
assert.equal(typeof catalog.tools.imageToText, "string", `${language} Image to Text title is missing`);
100+
assert.equal(typeof catalog.categories.image.toText, "string", `${language} Image to Text description is missing`);
101+
assert.notEqual(catalog.tools.imageToText, "tools.imageToText", `${language} Image to Text title exposes a raw key`);
102+
assert.notEqual(catalog.categories.image.toText, "categories.image.toText", `${language} Image to Text description exposes a raw key`);
71103
}
72104

73105
console.log("Category availability, semantic list, production route, and planned-state checks passed.");

‎tools/scan/index.html‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,20 @@
55
<link rel="icon" type="image/png" sizes="16x16" href="/assets/icons/favicon-16x16.png">
66
<link rel="apple-touch-icon" sizes="180x180" href="/assets/icons/apple-touch-icon.png">
77

8-
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' blob: data:; connect-src 'none'; object-src 'none'; frame-src 'none'; base-uri 'self'; form-action 'self'"><meta name="description" content="Local scanning and OCR tools planned for Secure Tools.">
8+
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' blob: data:; connect-src 'none'; object-src 'none'; frame-src 'none'; base-uri 'self'; form-action 'self'"><meta name="description" content="Local scanning and OCR tools for private documents.">
99
<link rel="canonical" href="https://tools.securetools.app/tools/scan/">
1010
<meta property="og:site_name" content="Secure Tools">
11-
<meta property="og:url" content="https://tools.securetools.app/tools/scan/"><meta name="theme-color" content="#f7f7f5"><meta property="og:type" content="website"><meta property="og:title" content="Scan and OCR tools — Secure Tools"><meta property="og:description" content="Local scanning and OCR tools planned for Secure Tools.">
11+
<meta property="og:url" content="https://tools.securetools.app/tools/scan/"><meta name="theme-color" content="#f7f7f5"><meta property="og:type" content="website"><meta property="og:title" content="Scan and OCR tools — Secure Tools"><meta property="og:description" content="Local scanning and OCR tools for private documents.">
1212
<meta property="og:image" content="https://tools.securetools.app/assets/images/og-image.png">
1313
<meta property="og:image:width" content="1200">
1414
<meta property="og:image:height" content="630">
1515
<meta property="og:image:alt" content="Secure Tools — Local-first browser utilities for PDF, Image, and Privacy">
1616
<meta name="twitter:card" content="summary_large_image">
1717
<meta name="twitter:title" content="Scan and OCR tools — Secure Tools">
18-
<meta name="twitter:description" content="Local scanning and OCR tools planned for Secure Tools.">
18+
<meta name="twitter:description" content="Local scanning and OCR tools for private documents.">
1919
<meta name="twitter:image" content="https://tools.securetools.app/assets/images/og-image.png">
2020
<meta name="twitter:image:alt" content="Secure Tools — Local-first browser utilities for PDF, Image, and Privacy">
2121
<title>Scan and OCR tools — Secure Tools</title><script src="../../js/theme-bootstrap.js"></script><link rel="stylesheet" href="../../css/base.css"><link rel="stylesheet" href="../../css/components.css"><link rel="stylesheet" href="../../css/pages.css"><link rel="stylesheet" href="../../css/categories.css"></head>
2222
<body class="page-shell" data-page="scanCategory"><a class="skip-link" href="#main" data-i18n="common.skip">Skip to main content</a><header class="site-header"><div class="container header-inner"><a class="brand" href="../../" aria-label="Secure Tools home" data-i18n-aria-label="common.aria.home"><span class="brand-mark" aria-hidden="true"></span><span>Secure Tools</span></a><nav class="site-nav" aria-label="Primary navigation" data-i18n-aria-label="common.aria.primaryNavigation"><a href="../../#tools" data-i18n="common.nav.tools">Tools</a><a href="../../privacy/" data-i18n="common.nav.privacy">Privacy</a><a href="../../about/" data-i18n="common.nav.about">About</a><a href="https://github.com/SecureToolsProject/Secure_Tools" data-repository-link target="_blank" rel="noreferrer" data-i18n="common.nav.source">Source</a></nav><div class="header-controls"><label class="control-label"><span data-i18n="common.language">Language</span><select class="control" data-language-select><option value="en">English</option><option value="ko">한국어</option><option value="ja">日本語</option><option value="es">Español</option><option value="de">Deutsch</option><option value="fr">Français</option></select></label><label class="control-label"><span data-i18n="common.theme">Theme</span><select class="control" data-theme-select><option value="system" data-i18n="common.themes.system">System</option><option value="light" data-i18n="common.themes.light">Light</option><option value="dark" data-i18n="common.themes.dark">Dark</option></select></label></div></div></header>
2323
<nav class="category-nav" aria-label="Tool categories" data-i18n-aria-label="common.aria.toolCategories"><div class="container category-nav__inner"><a href="../pdf/" data-i18n="categories.nav.pdf">PDF</a><a href="../image/" data-i18n="categories.nav.image">Image</a><a href="../privacy/" data-i18n="categories.nav.privacy">Privacy</a><a href="../scan/" aria-current="page" data-i18n="categories.nav.scan">Scan & OCR</a><a href="../media/" data-i18n="categories.nav.media">Media</a></div></nav>
24-
<main id="main" class="page-main category-main"><div class="container"><section class="category-intro"><div><p class="eyebrow" data-i18n="categories.eyebrow">Tool category</p><h1 data-i18n="categories.scan.title">Scan & OCR tools</h1></div><p data-i18n="categories.scan.description">Turn scans into useful documents while keeping source material on your device.</p></section><ul class="category-tool-list"><li><article class="category-tool surface"><span class="status" data-i18n="tools.comingSoon">Coming soon</span><h2 data-i18n="categories.scan.ocrTitle">Image to text</h2><p data-i18n="categories.scan.ocr">Recognize text from an image locally.</p></article></li><li><article class="category-tool surface"><span class="status" data-i18n="tools.comingSoon">Coming soon</span><h2 data-i18n="categories.scan.documentTitle">Document scanner</h2><p data-i18n="categories.scan.document">Crop and clean photographed documents.</p></article></li></ul><p class="category-note" data-i18n="categories.plannedNote">This category is planned. No unavailable item is presented as working.</p></div></main><footer class="site-footer"><div class="container footer-inner"><div><a class="brand" href="../../" aria-label="Secure Tools home" data-i18n-aria-label="common.aria.home"><span class="brand-mark" aria-hidden="true"></span><span>Secure Tools</span></a><p data-i18n="common.footerStatement">Your files stay on your device.</p></div><nav aria-label="Footer navigation" data-i18n-aria-label="common.aria.footerNavigation"><a href="../../#tools" data-i18n="common.nav.tools">Tools</a><a href="../../privacy/" data-i18n="common.nav.privacy">Privacy</a><a href="../../about/" data-i18n="common.nav.about">About</a><a href="https://github.com/SecureToolsProject/Secure_Tools" data-repository-link target="_blank" rel="noreferrer" data-i18n="common.nav.source">Source</a></nav></div></footer><script type="module" src="../../js/main.js"></script></body></html>
24+
<main id="main" class="page-main category-main"><div class="container"><section class="category-intro"><div><p class="eyebrow" data-i18n="categories.eyebrow">Tool category</p><h1 data-i18n="categories.scan.title">Scan & OCR tools</h1></div><p data-i18n="categories.scan.description">Turn scans into useful documents while keeping source material on your device.</p></section><ul class="category-tool-list"><li><a class="category-tool surface" href="../image/to-text/"><div class="category-tool__top"><span class="status status--available" data-i18n="tools.available">Available</span><span class="category-tool__arrow" aria-hidden="true">→</span></div><h2 data-i18n="tools.imageToText">Image to Text OCR</h2><p data-i18n="categories.image.toText">Extract editable text from an image with local English and Korean OCR.</p></a></li><li><article class="category-tool surface"><span class="status" data-i18n="tools.comingSoon">Coming soon</span><h2 data-i18n="categories.scan.documentTitle">Document scanner</h2><p data-i18n="categories.scan.document">Crop and clean photographed documents.</p></article></li></ul><p class="category-note" data-i18n="categories.localNote">Available tools process file contents locally in browser memory.</p></div></main><footer class="site-footer"><div class="container footer-inner"><div><a class="brand" href="../../" aria-label="Secure Tools home" data-i18n-aria-label="common.aria.home"><span class="brand-mark" aria-hidden="true"></span><span>Secure Tools</span></a><p data-i18n="common.footerStatement">Your files stay on your device.</p></div><nav aria-label="Footer navigation" data-i18n-aria-label="common.aria.footerNavigation"><a href="../../#tools" data-i18n="common.nav.tools">Tools</a><a href="../../privacy/" data-i18n="common.nav.privacy">Privacy</a><a href="../../about/" data-i18n="common.nav.about">About</a><a href="https://github.com/SecureToolsProject/Secure_Tools" data-repository-link target="_blank" rel="noreferrer" data-i18n="common.nav.source">Source</a></nav></div></footer><script type="module" src="../../js/main.js"></script></body></html>

0 commit comments

Comments
 (0)