mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2025-09-26 17:52:59 +02:00
Feature/v2/fuzzy tool search (#4482)
# Description of Changes <!-- Please provide a summary of the changes, including: - What was changed - Why the change was made - Any challenges encountered Closes #(issue_number) --> --- ## Checklist ### General - [x] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [x] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md) (if applicable) - [x] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md) (if applicable) - [x] I have performed a self-review of my own code - [x] My changes generate no new warnings ### Documentation - [ ] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed) - [ ] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only) ### UI Changes (if applicable) - [ ] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR) ### Testing (if applicable) - [ ] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing) for more details.
This commit is contained in:
parent
1219cebd07
commit
21b1428ab5
@ -357,222 +357,277 @@
|
|||||||
"globalPopularity": "Global Popularity",
|
"globalPopularity": "Global Popularity",
|
||||||
"sortBy": "Sort by:",
|
"sortBy": "Sort by:",
|
||||||
"multiTool": {
|
"multiTool": {
|
||||||
|
"tags": "multiple,tools",
|
||||||
"title": "PDF Multi Tool",
|
"title": "PDF Multi Tool",
|
||||||
"desc": "Merge, Rotate, Rearrange, Split, and Remove pages"
|
"desc": "Merge, Rotate, Rearrange, Split, and Remove pages"
|
||||||
},
|
},
|
||||||
"merge": {
|
"merge": {
|
||||||
|
"tags": "combine,join,unite",
|
||||||
"title": "Merge",
|
"title": "Merge",
|
||||||
"desc": "Easily merge multiple PDFs into one."
|
"desc": "Easily merge multiple PDFs into one."
|
||||||
},
|
},
|
||||||
"split": {
|
"split": {
|
||||||
|
"tags": "divide,separate,break",
|
||||||
"title": "Split",
|
"title": "Split",
|
||||||
"desc": "Split PDFs into multiple documents"
|
"desc": "Split PDFs into multiple documents"
|
||||||
},
|
},
|
||||||
"rotate": {
|
"rotate": {
|
||||||
|
"tags": "turn,flip,orient",
|
||||||
"title": "Rotate",
|
"title": "Rotate",
|
||||||
"desc": "Easily rotate your PDFs."
|
"desc": "Easily rotate your PDFs."
|
||||||
},
|
},
|
||||||
"convert": {
|
"convert": {
|
||||||
|
"tags": "transform,change",
|
||||||
"title": "Convert",
|
"title": "Convert",
|
||||||
"desc": "Convert files between different formats"
|
"desc": "Convert files between different formats"
|
||||||
},
|
},
|
||||||
"pdfOrganiser": {
|
"pdfOrganiser": {
|
||||||
|
"tags": "organize,rearrange,reorder",
|
||||||
"title": "Organise",
|
"title": "Organise",
|
||||||
"desc": "Remove/Rearrange pages in any order"
|
"desc": "Remove/Rearrange pages in any order"
|
||||||
},
|
},
|
||||||
"addImage": {
|
"addImage": {
|
||||||
|
"tags": "insert,embed,place",
|
||||||
"title": "Add image",
|
"title": "Add image",
|
||||||
"desc": "Adds a image onto a set location on the PDF"
|
"desc": "Adds a image onto a set location on the PDF"
|
||||||
},
|
},
|
||||||
"addAttachments": {
|
"addAttachments": {
|
||||||
|
"tags": "embed,attach,include",
|
||||||
"title": "Add Attachments",
|
"title": "Add Attachments",
|
||||||
"desc": "Add or remove embedded files (attachments) to/from a PDF"
|
"desc": "Add or remove embedded files (attachments) to/from a PDF"
|
||||||
},
|
},
|
||||||
"watermark": {
|
"watermark": {
|
||||||
|
"tags": "stamp,mark,overlay",
|
||||||
"title": "Add Watermark",
|
"title": "Add Watermark",
|
||||||
"desc": "Add a custom watermark to your PDF document."
|
"desc": "Add a custom watermark to your PDF document."
|
||||||
},
|
},
|
||||||
"removePassword": {
|
"removePassword": {
|
||||||
|
"tags": "unlock",
|
||||||
"title": "Remove Password",
|
"title": "Remove Password",
|
||||||
"desc": "Remove password protection from your PDF document."
|
"desc": "Remove password protection from your PDF document."
|
||||||
},
|
},
|
||||||
"compress": {
|
"compress": {
|
||||||
|
"tags": "shrink,reduce,optimize",
|
||||||
"title": "Compress",
|
"title": "Compress",
|
||||||
"desc": "Compress PDFs to reduce their file size."
|
"desc": "Compress PDFs to reduce their file size."
|
||||||
},
|
},
|
||||||
"unlockPDFForms": {
|
"unlockPDFForms": {
|
||||||
|
"tags": "unlock,enable,edit",
|
||||||
"title": "Unlock PDF Forms",
|
"title": "Unlock PDF Forms",
|
||||||
"desc": "Remove read-only property of form fields in a PDF document."
|
"desc": "Remove read-only property of form fields in a PDF document."
|
||||||
},
|
},
|
||||||
"changeMetadata": {
|
"changeMetadata": {
|
||||||
|
"tags": "edit,modify,update",
|
||||||
"title": "Change Metadata",
|
"title": "Change Metadata",
|
||||||
"desc": "Change/Remove/Add metadata from a PDF document"
|
"desc": "Change/Remove/Add metadata from a PDF document"
|
||||||
},
|
},
|
||||||
"ocr": {
|
"ocr": {
|
||||||
|
"tags": "extract,scan",
|
||||||
"title": "OCR / Cleanup scans",
|
"title": "OCR / Cleanup scans",
|
||||||
"desc": "Cleanup scans and detects text from images within a PDF and re-adds it as text."
|
"desc": "Cleanup scans and detects text from images within a PDF and re-adds it as text."
|
||||||
},
|
},
|
||||||
"extractImages": {
|
"extractImages": {
|
||||||
|
"tags": "pull,save,export",
|
||||||
"title": "Extract Images",
|
"title": "Extract Images",
|
||||||
"desc": "Extracts all images from a PDF and saves them to zip"
|
"desc": "Extracts all images from a PDF and saves them to zip"
|
||||||
},
|
},
|
||||||
"scannerImageSplit": {
|
"scannerImageSplit": {
|
||||||
|
"tags": "detect,split,photos",
|
||||||
"title": "Detect/Split Scanned photos",
|
"title": "Detect/Split Scanned photos",
|
||||||
"desc": "Splits multiple photos from within a photo/PDF"
|
"desc": "Splits multiple photos from within a photo/PDF"
|
||||||
},
|
},
|
||||||
"sign": {
|
"sign": {
|
||||||
|
"tags": "signature,autograph",
|
||||||
"title": "Sign",
|
"title": "Sign",
|
||||||
"desc": "Adds signature to PDF by drawing, text or image"
|
"desc": "Adds signature to PDF by drawing, text or image"
|
||||||
},
|
},
|
||||||
"flatten": {
|
"flatten": {
|
||||||
|
"tags": "simplify,remove,interactive",
|
||||||
"title": "Flatten",
|
"title": "Flatten",
|
||||||
"desc": "Remove all interactive elements and forms from a PDF"
|
"desc": "Remove all interactive elements and forms from a PDF"
|
||||||
},
|
},
|
||||||
"certSign": {
|
"certSign": {
|
||||||
|
"tags": "authenticate,PEM,P12,official,encrypt,sign,certificate,PKCS12,JKS,server,manual,auto",
|
||||||
"title": "Sign with Certificate",
|
"title": "Sign with Certificate",
|
||||||
"desc": "Signs a PDF with a Certificate/Key (PEM/P12)"
|
"desc": "Signs a PDF with a Certificate/Key (PEM/P12)"
|
||||||
},
|
},
|
||||||
"repair": {
|
"repair": {
|
||||||
|
"tags": "fix,restore",
|
||||||
"title": "Repair",
|
"title": "Repair",
|
||||||
"desc": "Tries to repair a corrupt/broken PDF"
|
"desc": "Tries to repair a corrupt/broken PDF"
|
||||||
},
|
},
|
||||||
"removeBlanks": {
|
"removeBlanks": {
|
||||||
|
"tags": "delete,clean,empty",
|
||||||
"title": "Remove Blank pages",
|
"title": "Remove Blank pages",
|
||||||
"desc": "Detects and removes blank pages from a document"
|
"desc": "Detects and removes blank pages from a document"
|
||||||
},
|
},
|
||||||
"removeAnnotations": {
|
"removeAnnotations": {
|
||||||
|
"tags": "delete,clean,strip",
|
||||||
"title": "Remove Annotations",
|
"title": "Remove Annotations",
|
||||||
"desc": "Removes all comments/annotations from a PDF"
|
"desc": "Removes all comments/annotations from a PDF"
|
||||||
},
|
},
|
||||||
"compare": {
|
"compare": {
|
||||||
|
"tags": "difference",
|
||||||
"title": "Compare",
|
"title": "Compare",
|
||||||
"desc": "Compares and shows the differences between 2 PDF Documents"
|
"desc": "Compares and shows the differences between 2 PDF Documents"
|
||||||
},
|
},
|
||||||
"removeCertSign": {
|
"removeCertSign": {
|
||||||
|
"tags": "remove,delete,unlock",
|
||||||
"title": "Remove Certificate Sign",
|
"title": "Remove Certificate Sign",
|
||||||
"desc": "Remove certificate signature from PDF"
|
"desc": "Remove certificate signature from PDF"
|
||||||
},
|
},
|
||||||
"pageLayout": {
|
"pageLayout": {
|
||||||
|
"tags": "layout,arrange,combine",
|
||||||
"title": "Multi-Page Layout",
|
"title": "Multi-Page Layout",
|
||||||
"desc": "Merge multiple pages of a PDF document into a single page"
|
"desc": "Merge multiple pages of a PDF document into a single page"
|
||||||
},
|
},
|
||||||
"bookletImposition": {
|
"bookletImposition": {
|
||||||
|
"tags": "booklet,print,binding",
|
||||||
"title": "Booklet Imposition",
|
"title": "Booklet Imposition",
|
||||||
"desc": "Create booklets with proper page ordering and multi-page layout for printing and binding"
|
"desc": "Create booklets with proper page ordering and multi-page layout for printing and binding"
|
||||||
},
|
},
|
||||||
"scalePages": {
|
"scalePages": {
|
||||||
|
"tags": "resize,adjust,scale",
|
||||||
"title": "Adjust page size/scale",
|
"title": "Adjust page size/scale",
|
||||||
"desc": "Change the size/scale of a page and/or its contents."
|
"desc": "Change the size/scale of a page and/or its contents."
|
||||||
},
|
},
|
||||||
"addPageNumbers": {
|
"addPageNumbers": {
|
||||||
|
"tags": "number,pagination,count",
|
||||||
"title": "Add Page Numbers",
|
"title": "Add Page Numbers",
|
||||||
"desc": "Add Page numbers throughout a document in a set location"
|
"desc": "Add Page numbers throughout a document in a set location"
|
||||||
},
|
},
|
||||||
"autoRename": {
|
"autoRename": {
|
||||||
|
"tags": "auto-detect,header-based,organize,relabel",
|
||||||
"title": "Auto Rename PDF File",
|
"title": "Auto Rename PDF File",
|
||||||
"desc": "Auto renames a PDF file based on its detected header"
|
"desc": "Auto renames a PDF file based on its detected header"
|
||||||
},
|
},
|
||||||
"adjustContrast": {
|
"adjustContrast": {
|
||||||
|
"tags": "contrast,brightness,saturation",
|
||||||
"title": "Adjust Colours/Contrast",
|
"title": "Adjust Colours/Contrast",
|
||||||
"desc": "Adjust Contrast, Saturation and Brightness of a PDF"
|
"desc": "Adjust Contrast, Saturation and Brightness of a PDF"
|
||||||
},
|
},
|
||||||
"crop": {
|
"crop": {
|
||||||
|
"tags": "trim,cut,resize",
|
||||||
"title": "Crop PDF",
|
"title": "Crop PDF",
|
||||||
"desc": "Crop a PDF to reduce its size (maintains text!)"
|
"desc": "Crop a PDF to reduce its size (maintains text!)"
|
||||||
},
|
},
|
||||||
"autoSplitPDF": {
|
"autoSplitPDF": {
|
||||||
|
"tags": "auto,split,QR",
|
||||||
"title": "Auto Split Pages",
|
"title": "Auto Split Pages",
|
||||||
"desc": "Auto Split Scanned PDF with physical scanned page splitter QR Code"
|
"desc": "Auto Split Scanned PDF with physical scanned page splitter QR Code"
|
||||||
},
|
},
|
||||||
"sanitize": {
|
"sanitize": {
|
||||||
|
"tags": "clean,purge,remove",
|
||||||
"title": "Sanitise",
|
"title": "Sanitise",
|
||||||
"desc": "Remove potentially harmful elements from PDF files"
|
"desc": "Remove potentially harmful elements from PDF files"
|
||||||
},
|
},
|
||||||
"getPdfInfo": {
|
"getPdfInfo": {
|
||||||
|
"tags": "info,metadata,details",
|
||||||
"title": "Get ALL Info on PDF",
|
"title": "Get ALL Info on PDF",
|
||||||
"desc": "Grabs any and all information possible on PDFs"
|
"desc": "Grabs any and all information possible on PDFs"
|
||||||
},
|
},
|
||||||
"pdfToSinglePage": {
|
"pdfToSinglePage": {
|
||||||
|
"tags": "combine,merge,single",
|
||||||
"title": "PDF to Single Large Page",
|
"title": "PDF to Single Large Page",
|
||||||
"desc": "Merges all PDF pages into one large single page"
|
"desc": "Merges all PDF pages into one large single page"
|
||||||
},
|
},
|
||||||
"showJS": {
|
"showJS": {
|
||||||
|
"tags": "javascript,code,script",
|
||||||
"title": "Show Javascript",
|
"title": "Show Javascript",
|
||||||
"desc": "Searches and displays any JS injected into a PDF"
|
"desc": "Searches and displays any JS injected into a PDF"
|
||||||
},
|
},
|
||||||
"redact": {
|
"redact": {
|
||||||
|
"tags": "censor,blackout,hide",
|
||||||
"title": "Redact",
|
"title": "Redact",
|
||||||
"desc": "Redacts (blacks out) a PDF based on selected text, drawn shapes and/or selected page(s)"
|
"desc": "Redacts (blacks out) a PDF based on selected text, drawn shapes and/or selected page(s)"
|
||||||
},
|
},
|
||||||
"overlayPdfs": {
|
"overlayPdfs": {
|
||||||
|
"tags": "overlay,combine,stack",
|
||||||
"title": "Overlay PDFs",
|
"title": "Overlay PDFs",
|
||||||
"desc": "Overlays PDFs on-top of another PDF"
|
"desc": "Overlays PDFs on-top of another PDF"
|
||||||
},
|
},
|
||||||
"splitBySections": {
|
"splitBySections": {
|
||||||
|
"tags": "split,sections,divide",
|
||||||
"title": "Split PDF by Sections",
|
"title": "Split PDF by Sections",
|
||||||
"desc": "Divide each page of a PDF into smaller horizontal and vertical sections"
|
"desc": "Divide each page of a PDF into smaller horizontal and vertical sections"
|
||||||
},
|
},
|
||||||
"addStamp": {
|
"addStamp": {
|
||||||
|
"tags": "stamp,mark,seal",
|
||||||
"title": "Add Stamp to PDF",
|
"title": "Add Stamp to PDF",
|
||||||
"desc": "Add text or add image stamps at set locations"
|
"desc": "Add text or add image stamps at set locations"
|
||||||
},
|
},
|
||||||
"removeImage": {
|
"removeImage": {
|
||||||
|
"tags": "remove,delete,clean",
|
||||||
"title": "Remove image",
|
"title": "Remove image",
|
||||||
"desc": "Remove image from PDF to reduce file size"
|
"desc": "Remove image from PDF to reduce file size"
|
||||||
},
|
},
|
||||||
"splitByChapters": {
|
"splitByChapters": {
|
||||||
|
"tags": "split,chapters,structure",
|
||||||
"title": "Split PDF by Chapters",
|
"title": "Split PDF by Chapters",
|
||||||
"desc": "Split a PDF into multiple files based on its chapter structure."
|
"desc": "Split a PDF into multiple files based on its chapter structure."
|
||||||
},
|
},
|
||||||
"validateSignature": {
|
"validateSignature": {
|
||||||
|
"tags": "validate,verify,certificate",
|
||||||
"title": "Validate PDF Signature",
|
"title": "Validate PDF Signature",
|
||||||
"desc": "Verify digital signatures and certificates in PDF documents"
|
"desc": "Verify digital signatures and certificates in PDF documents"
|
||||||
},
|
},
|
||||||
"swagger": {
|
"swagger": {
|
||||||
|
"tags": "API,documentation,test",
|
||||||
"title": "API Documentation",
|
"title": "API Documentation",
|
||||||
"desc": "View API documentation and test endpoints"
|
"desc": "View API documentation and test endpoints"
|
||||||
},
|
},
|
||||||
"fakeScan": {
|
"fakeScan": {
|
||||||
|
"tags": "scan,simulate,create",
|
||||||
"title": "Fake Scan",
|
"title": "Fake Scan",
|
||||||
"desc": "Create a PDF that looks like it was scanned"
|
"desc": "Create a PDF that looks like it was scanned"
|
||||||
},
|
},
|
||||||
"editTableOfContents": {
|
"editTableOfContents": {
|
||||||
|
"tags": "bookmarks,contents,edit",
|
||||||
"title": "Edit Table of Contents",
|
"title": "Edit Table of Contents",
|
||||||
"desc": "Add or edit bookmarks and table of contents in PDF documents"
|
"desc": "Add or edit bookmarks and table of contents in PDF documents"
|
||||||
},
|
},
|
||||||
"manageCertificates": {
|
"manageCertificates": {
|
||||||
|
"tags": "certificates,import,export",
|
||||||
"title": "Manage Certificates",
|
"title": "Manage Certificates",
|
||||||
"desc": "Import, export, or delete digital certificate files used for signing PDFs."
|
"desc": "Import, export, or delete digital certificate files used for signing PDFs."
|
||||||
},
|
},
|
||||||
"read": {
|
"read": {
|
||||||
|
"tags": "view,open,display",
|
||||||
"title": "Read",
|
"title": "Read",
|
||||||
"desc": "View and annotate PDFs. Highlight text, draw, or insert comments for review and collaboration."
|
"desc": "View and annotate PDFs. Highlight text, draw, or insert comments for review and collaboration."
|
||||||
},
|
},
|
||||||
"reorganizePages": {
|
"reorganizePages": {
|
||||||
|
"tags": "rearrange,reorder,organize",
|
||||||
"title": "Reorganize Pages",
|
"title": "Reorganize Pages",
|
||||||
"desc": "Rearrange, duplicate, or delete PDF pages with visual drag-and-drop control."
|
"desc": "Rearrange, duplicate, or delete PDF pages with visual drag-and-drop control."
|
||||||
},
|
},
|
||||||
"extractPages": {
|
"extractPages": {
|
||||||
|
"tags": "pull,select,copy",
|
||||||
"title": "Extract Pages",
|
"title": "Extract Pages",
|
||||||
"desc": "Extract specific pages from a PDF document"
|
"desc": "Extract specific pages from a PDF document"
|
||||||
},
|
},
|
||||||
"removePages": {
|
"removePages": {
|
||||||
|
"tags": "delete,extract,exclude",
|
||||||
"title": "Remove Pages",
|
"title": "Remove Pages",
|
||||||
"desc": "Remove specific pages from a PDF document"
|
"desc": "Remove specific pages from a PDF document"
|
||||||
},
|
},
|
||||||
"autoSizeSplitPDF": {
|
"autoSizeSplitPDF": {
|
||||||
|
"tags": "auto,split,size",
|
||||||
"title": "Auto Split by Size/Count",
|
"title": "Auto Split by Size/Count",
|
||||||
"desc": "Automatically split PDFs by file size or page count"
|
"desc": "Automatically split PDFs by file size or page count"
|
||||||
},
|
},
|
||||||
"replaceColorPdf": {
|
"replaceColorPdf": {
|
||||||
|
"tags": "color,replace,invert",
|
||||||
"title": "Replace & Invert Colour",
|
"title": "Replace & Invert Colour",
|
||||||
"desc": "Replace or invert colours in PDF documents"
|
"desc": "Replace or invert colours in PDF documents"
|
||||||
},
|
},
|
||||||
"devApi": {
|
"devApi": {
|
||||||
|
"tags": "API,development,documentation",
|
||||||
"title": "API",
|
"title": "API",
|
||||||
"desc": "Link to API documentation"
|
"desc": "Link to API documentation"
|
||||||
},
|
},
|
||||||
"devFolderScanning": {
|
"devFolderScanning": {
|
||||||
|
"tags": "automation,folder,scanning",
|
||||||
"title": "Automated Folder Scanning",
|
"title": "Automated Folder Scanning",
|
||||||
"desc": "Link to automated folder scanning guide"
|
"desc": "Link to automated folder scanning guide"
|
||||||
},
|
},
|
||||||
@ -593,6 +648,7 @@
|
|||||||
"desc": "Change document restrictions and permissions"
|
"desc": "Change document restrictions and permissions"
|
||||||
},
|
},
|
||||||
"automate": {
|
"automate": {
|
||||||
|
"tags": "workflow,sequence,automation",
|
||||||
"title": "Automate",
|
"title": "Automate",
|
||||||
"desc": "Build multi-step workflows by chaining together PDF actions. Ideal for recurring tasks."
|
"desc": "Build multi-step workflows by chaining together PDF actions. Ideal for recurring tasks."
|
||||||
}
|
}
|
||||||
@ -659,7 +715,6 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"split": {
|
"split": {
|
||||||
"tags": "Page operations,divide,Multi Page,cut,server side",
|
|
||||||
"title": "Split PDF",
|
"title": "Split PDF",
|
||||||
"header": "Split PDF",
|
"header": "Split PDF",
|
||||||
"desc": {
|
"desc": {
|
||||||
@ -785,7 +840,6 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"rotate": {
|
"rotate": {
|
||||||
"tags": "server side",
|
|
||||||
"title": "Rotate PDF",
|
"title": "Rotate PDF",
|
||||||
"submit": "Apply Rotation",
|
"submit": "Apply Rotation",
|
||||||
"error": {
|
"error": {
|
||||||
@ -1298,7 +1352,6 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"changeMetadata": {
|
"changeMetadata": {
|
||||||
"tags": "Title,author,date,creation,time,publisher,producer,stats",
|
|
||||||
"header": "Change Metadata",
|
"header": "Change Metadata",
|
||||||
"submit": "Change",
|
"submit": "Change",
|
||||||
"filenamePrefix": "metadata",
|
"filenamePrefix": "metadata",
|
||||||
@ -1613,7 +1666,6 @@
|
|||||||
"info": "Python is not installed. It is required to run."
|
"info": "Python is not installed. It is required to run."
|
||||||
},
|
},
|
||||||
"sign": {
|
"sign": {
|
||||||
"tags": "authorize,initials,drawn-signature,text-sign,image-signature",
|
|
||||||
"title": "Sign",
|
"title": "Sign",
|
||||||
"header": "Sign PDFs",
|
"header": "Sign PDFs",
|
||||||
"upload": "Upload Image",
|
"upload": "Upload Image",
|
||||||
@ -1637,7 +1689,6 @@
|
|||||||
"redo": "Redo"
|
"redo": "Redo"
|
||||||
},
|
},
|
||||||
"flatten": {
|
"flatten": {
|
||||||
"tags": "static,deactivate,non-interactive,streamline",
|
|
||||||
"title": "Flatten",
|
"title": "Flatten",
|
||||||
"header": "Flatten PDF",
|
"header": "Flatten PDF",
|
||||||
"flattenOnlyForms": "Flatten only forms",
|
"flattenOnlyForms": "Flatten only forms",
|
||||||
@ -1702,7 +1753,6 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"removeBlanks": {
|
"removeBlanks": {
|
||||||
"tags": "cleanup,streamline,non-content,organize",
|
|
||||||
"title": "Remove Blanks",
|
"title": "Remove Blanks",
|
||||||
"header": "Remove Blank Pages",
|
"header": "Remove Blank Pages",
|
||||||
"settings": {
|
"settings": {
|
||||||
@ -2099,7 +2149,6 @@
|
|||||||
"tags": "color-correction,tune,modify,enhance,colour-correction"
|
"tags": "color-correction,tune,modify,enhance,colour-correction"
|
||||||
},
|
},
|
||||||
"crop": {
|
"crop": {
|
||||||
"tags": "trim,shrink,edit,shape",
|
|
||||||
"title": "Crop",
|
"title": "Crop",
|
||||||
"header": "Crop PDF",
|
"header": "Crop PDF",
|
||||||
"submit": "Apply Crop",
|
"submit": "Apply Crop",
|
||||||
|
@ -348,206 +348,257 @@
|
|||||||
"globalPopularity": "Global Popularity",
|
"globalPopularity": "Global Popularity",
|
||||||
"sortBy": "Sort by:",
|
"sortBy": "Sort by:",
|
||||||
"multiTool": {
|
"multiTool": {
|
||||||
|
"tags": "multiple,tools",
|
||||||
"title": "PDF Multi Tool",
|
"title": "PDF Multi Tool",
|
||||||
"desc": "Merge, Rotate, Rearrange, Split, and Remove pages"
|
"desc": "Merge, Rotate, Rearrange, Split, and Remove pages"
|
||||||
},
|
},
|
||||||
"merge": {
|
"merge": {
|
||||||
|
"tags": "combine,join,unite",
|
||||||
"title": "Merge",
|
"title": "Merge",
|
||||||
"desc": "Easily merge multiple PDFs into one."
|
"desc": "Easily merge multiple PDFs into one."
|
||||||
},
|
},
|
||||||
"split": {
|
"split": {
|
||||||
|
"tags": "divide,separate,break",
|
||||||
"title": "Split",
|
"title": "Split",
|
||||||
"desc": "Split PDFs into multiple documents"
|
"desc": "Split PDFs into multiple documents"
|
||||||
},
|
},
|
||||||
"rotate": {
|
"rotate": {
|
||||||
|
"tags": "turn,flip,orient",
|
||||||
"title": "Rotate",
|
"title": "Rotate",
|
||||||
"desc": "Easily rotate your PDFs."
|
"desc": "Easily rotate your PDFs."
|
||||||
},
|
},
|
||||||
"imageToPDF": {
|
"imageToPDF": {
|
||||||
|
"tags": "convert,image,transform",
|
||||||
"title": "Image to PDF",
|
"title": "Image to PDF",
|
||||||
"desc": "Convert a image (PNG, JPEG, GIF) to PDF."
|
"desc": "Convert a image (PNG, JPEG, GIF) to PDF."
|
||||||
},
|
},
|
||||||
"pdfToImage": {
|
"pdfToImage": {
|
||||||
|
"tags": "convert,image,extract",
|
||||||
"title": "PDF to Image",
|
"title": "PDF to Image",
|
||||||
"desc": "Convert a PDF to a image. (PNG, JPEG, GIF)"
|
"desc": "Convert a PDF to a image. (PNG, JPEG, GIF)"
|
||||||
},
|
},
|
||||||
"pdfOrganiser": {
|
"pdfOrganiser": {
|
||||||
|
"tags": "organize,rearrange,reorder",
|
||||||
"title": "Organize",
|
"title": "Organize",
|
||||||
"desc": "Remove/Rearrange pages in any order"
|
"desc": "Remove/Rearrange pages in any order"
|
||||||
},
|
},
|
||||||
"addImage": {
|
"addImage": {
|
||||||
|
"tags": "insert,embed,place",
|
||||||
"title": "Add image",
|
"title": "Add image",
|
||||||
"desc": "Adds a image onto a set location on the PDF"
|
"desc": "Adds a image onto a set location on the PDF"
|
||||||
},
|
},
|
||||||
"watermark": {
|
"watermark": {
|
||||||
|
"tags": "stamp,mark,overlay",
|
||||||
"title": "Add Watermark",
|
"title": "Add Watermark",
|
||||||
"desc": "Add a custom watermark to your PDF document."
|
"desc": "Add a custom watermark to your PDF document."
|
||||||
},
|
},
|
||||||
"permissions": {
|
"permissions": {
|
||||||
|
"tags": "permissions,security,access",
|
||||||
"title": "Change Permissions",
|
"title": "Change Permissions",
|
||||||
"desc": "Change the permissions of your PDF document"
|
"desc": "Change the permissions of your PDF document"
|
||||||
},
|
},
|
||||||
"pageRemover": {
|
"pageRemover": {
|
||||||
|
"tags": "remove,delete,pages",
|
||||||
"title": "Remove",
|
"title": "Remove",
|
||||||
"desc": "Delete unwanted pages from your PDF document."
|
"desc": "Delete unwanted pages from your PDF document."
|
||||||
},
|
},
|
||||||
"addPassword": {
|
"addPassword": {
|
||||||
|
"tags": "password,encrypt,secure",
|
||||||
"title": "Add Password",
|
"title": "Add Password",
|
||||||
"desc": "Encrypt your PDF document with a password."
|
"desc": "Encrypt your PDF document with a password."
|
||||||
},
|
},
|
||||||
"changePermissions": {
|
"changePermissions": {
|
||||||
|
"tags": "permissions,restrictions,security",
|
||||||
"title": "Change Permissions",
|
"title": "Change Permissions",
|
||||||
"desc": "Change document restrictions and permissions."
|
"desc": "Change document restrictions and permissions."
|
||||||
},
|
},
|
||||||
"removePassword": {
|
"removePassword": {
|
||||||
|
"tags": "unlock,remove,password",
|
||||||
"title": "Remove Password",
|
"title": "Remove Password",
|
||||||
"desc": "Remove password protection from your PDF document."
|
"desc": "Remove password protection from your PDF document."
|
||||||
},
|
},
|
||||||
"compress": {
|
"compress": {
|
||||||
|
"tags": "shrink,reduce,optimize",
|
||||||
"title": "Compress",
|
"title": "Compress",
|
||||||
"desc": "Compress PDFs to reduce their file size."
|
"desc": "Compress PDFs to reduce their file size."
|
||||||
},
|
},
|
||||||
"sanitize": {
|
"sanitize": {
|
||||||
|
"tags": "clean,purge,remove",
|
||||||
"title": "Sanitize",
|
"title": "Sanitize",
|
||||||
"desc": "Remove potentially harmful elements from PDF files."
|
"desc": "Remove potentially harmful elements from PDF files."
|
||||||
},
|
},
|
||||||
"unlockPDFForms": {
|
"unlockPDFForms": {
|
||||||
|
"tags": "unlock,enable,edit",
|
||||||
"title": "Unlock PDF Forms",
|
"title": "Unlock PDF Forms",
|
||||||
"desc": "Remove read-only property of form fields in a PDF document."
|
"desc": "Remove read-only property of form fields in a PDF document."
|
||||||
},
|
},
|
||||||
"changeMetadata": {
|
"changeMetadata": {
|
||||||
|
"tags": "edit,modify,update",
|
||||||
"title": "Change Metadata",
|
"title": "Change Metadata",
|
||||||
"desc": "Change/Remove/Add metadata from a PDF document"
|
"desc": "Change/Remove/Add metadata from a PDF document"
|
||||||
},
|
},
|
||||||
"fileToPDF": {
|
"fileToPDF": {
|
||||||
|
"tags": "convert,transform,change",
|
||||||
"title": "Convert file to PDF",
|
"title": "Convert file to PDF",
|
||||||
"desc": "Convert nearly any file to PDF (DOCX, PNG, XLS, PPT, TXT and more)"
|
"desc": "Convert nearly any file to PDF (DOCX, PNG, XLS, PPT, TXT and more)"
|
||||||
},
|
},
|
||||||
"ocr": {
|
"ocr": {
|
||||||
|
"tags": "extract,scan",
|
||||||
"title": "OCR / Cleanup scans",
|
"title": "OCR / Cleanup scans",
|
||||||
"desc": "Cleanup scans and detects text from images within a PDF and re-adds it as text."
|
"desc": "Cleanup scans and detects text from images within a PDF and re-adds it as text."
|
||||||
},
|
},
|
||||||
"extractImages": {
|
"extractImages": {
|
||||||
|
"tags": "pull,save,export",
|
||||||
"title": "Extract Images",
|
"title": "Extract Images",
|
||||||
"desc": "Extracts all images from a PDF and saves them to zip"
|
"desc": "Extracts all images from a PDF and saves them to zip"
|
||||||
},
|
},
|
||||||
"pdfToPDFA": {
|
"pdfToPDFA": {
|
||||||
|
"tags": "convert,archive,long-term",
|
||||||
"title": "PDF to PDF/A",
|
"title": "PDF to PDF/A",
|
||||||
"desc": "Convert PDF to PDF/A for long-term storage"
|
"desc": "Convert PDF to PDF/A for long-term storage"
|
||||||
},
|
},
|
||||||
"PDFToWord": {
|
"PDFToWord": {
|
||||||
|
"tags": "convert,word,doc",
|
||||||
"title": "PDF to Word",
|
"title": "PDF to Word",
|
||||||
"desc": "Convert PDF to Word formats (DOC, DOCX and ODT)"
|
"desc": "Convert PDF to Word formats (DOC, DOCX and ODT)"
|
||||||
},
|
},
|
||||||
"PDFToPresentation": {
|
"PDFToPresentation": {
|
||||||
|
"tags": "convert,presentation,ppt",
|
||||||
"title": "PDF to Presentation",
|
"title": "PDF to Presentation",
|
||||||
"desc": "Convert PDF to Presentation formats (PPT, PPTX and ODP)"
|
"desc": "Convert PDF to Presentation formats (PPT, PPTX and ODP)"
|
||||||
},
|
},
|
||||||
"PDFToText": {
|
"PDFToText": {
|
||||||
|
"tags": "convert,text,rtf",
|
||||||
"title": "PDF to RTF (Text)",
|
"title": "PDF to RTF (Text)",
|
||||||
"desc": "Convert PDF to Text or RTF format"
|
"desc": "Convert PDF to Text or RTF format"
|
||||||
},
|
},
|
||||||
"PDFToHTML": {
|
"PDFToHTML": {
|
||||||
|
"tags": "convert,html,web",
|
||||||
"title": "PDF to HTML",
|
"title": "PDF to HTML",
|
||||||
"desc": "Convert PDF to HTML format"
|
"desc": "Convert PDF to HTML format"
|
||||||
},
|
},
|
||||||
"PDFToXML": {
|
"PDFToXML": {
|
||||||
|
"tags": "convert,xml,data",
|
||||||
"title": "PDF to XML",
|
"title": "PDF to XML",
|
||||||
"desc": "Convert PDF to XML format"
|
"desc": "Convert PDF to XML format"
|
||||||
},
|
},
|
||||||
"ScannerImageSplit": {
|
"ScannerImageSplit": {
|
||||||
|
"tags": "detect,split,photos",
|
||||||
"title": "Detect/Split Scanned photos",
|
"title": "Detect/Split Scanned photos",
|
||||||
"desc": "Splits multiple photos from within a photo/PDF"
|
"desc": "Splits multiple photos from within a photo/PDF"
|
||||||
},
|
},
|
||||||
"sign": {
|
"sign": {
|
||||||
|
"tags": "signature,autograph",
|
||||||
"title": "Sign",
|
"title": "Sign",
|
||||||
"desc": "Adds signature to PDF by drawing, text or image"
|
"desc": "Adds signature to PDF by drawing, text or image"
|
||||||
},
|
},
|
||||||
"flatten": {
|
"flatten": {
|
||||||
|
"tags": "simplify,remove,interactive",
|
||||||
"title": "Flatten",
|
"title": "Flatten",
|
||||||
"desc": "Remove all interactive elements and forms from a PDF"
|
"desc": "Remove all interactive elements and forms from a PDF"
|
||||||
},
|
},
|
||||||
"repair": {
|
"repair": {
|
||||||
|
"tags": "fix,restore",
|
||||||
"title": "Repair",
|
"title": "Repair",
|
||||||
"desc": "Tries to repair a corrupt/broken PDF"
|
"desc": "Tries to repair a corrupt/broken PDF"
|
||||||
},
|
},
|
||||||
"removeBlanks": {
|
"removeBlanks": {
|
||||||
|
"tags": "delete,clean,empty",
|
||||||
"title": "Remove Blank pages",
|
"title": "Remove Blank pages",
|
||||||
"desc": "Detects and removes blank pages from a document"
|
"desc": "Detects and removes blank pages from a document"
|
||||||
},
|
},
|
||||||
"removeAnnotations": {
|
"removeAnnotations": {
|
||||||
|
"tags": "delete,clean,strip",
|
||||||
"title": "Remove Annotations",
|
"title": "Remove Annotations",
|
||||||
"desc": "Removes all comments/annotations from a PDF"
|
"desc": "Removes all comments/annotations from a PDF"
|
||||||
},
|
},
|
||||||
"compare": {
|
"compare": {
|
||||||
|
"tags": "difference",
|
||||||
"title": "Compare",
|
"title": "Compare",
|
||||||
"desc": "Compares and shows the differences between 2 PDF Documents"
|
"desc": "Compares and shows the differences between 2 PDF Documents"
|
||||||
},
|
},
|
||||||
"certSign": {
|
"certSign": {
|
||||||
|
"tags": "authenticate,PEM,P12,official,encrypt,sign,certificate,PKCS12,JKS,server,manual,auto",
|
||||||
"title": "Sign with Certificate",
|
"title": "Sign with Certificate",
|
||||||
"desc": "Signs a PDF with a Certificate/Key (PEM/P12)"
|
"desc": "Signs a PDF with a Certificate/Key (PEM/P12)"
|
||||||
},
|
},
|
||||||
"removeCertSign": {
|
"removeCertSign": {
|
||||||
|
"tags": "remove,delete,unlock",
|
||||||
"title": "Remove Certificate Sign",
|
"title": "Remove Certificate Sign",
|
||||||
"desc": "Remove certificate signature from PDF"
|
"desc": "Remove certificate signature from PDF"
|
||||||
},
|
},
|
||||||
"pageLayout": {
|
"pageLayout": {
|
||||||
|
"tags": "layout,arrange,combine",
|
||||||
"title": "Multi-Page Layout",
|
"title": "Multi-Page Layout",
|
||||||
"desc": "Merge multiple pages of a PDF document into a single page"
|
"desc": "Merge multiple pages of a PDF document into a single page"
|
||||||
},
|
},
|
||||||
"bookletImposition": {
|
"bookletImposition": {
|
||||||
|
"tags": "booklet,print,binding",
|
||||||
"title": "Booklet Imposition",
|
"title": "Booklet Imposition",
|
||||||
"desc": "Create booklets with proper page ordering and multi-page layout for printing and binding"
|
"desc": "Create booklets with proper page ordering and multi-page layout for printing and binding"
|
||||||
},
|
},
|
||||||
"scalePages": {
|
"scalePages": {
|
||||||
|
"tags": "resize,adjust,scale",
|
||||||
"title": "Adjust page size/scale",
|
"title": "Adjust page size/scale",
|
||||||
"desc": "Change the size/scale of a page and/or its contents."
|
"desc": "Change the size/scale of a page and/or its contents."
|
||||||
},
|
},
|
||||||
"pipeline": {
|
"pipeline": {
|
||||||
|
"tags": "automation,script,workflow",
|
||||||
"title": "Pipeline",
|
"title": "Pipeline",
|
||||||
"desc": "Run multiple actions on PDFs by defining pipeline scripts"
|
"desc": "Run multiple actions on PDFs by defining pipeline scripts"
|
||||||
},
|
},
|
||||||
"addPageNumbers": {
|
"addPageNumbers": {
|
||||||
|
"tags": "number,pagination,count",
|
||||||
"title": "Add Page Numbers",
|
"title": "Add Page Numbers",
|
||||||
"desc": "Add Page numbers throughout a document in a set location"
|
"desc": "Add Page numbers throughout a document in a set location"
|
||||||
},
|
},
|
||||||
"auto-rename": {
|
"auto-rename": {
|
||||||
|
"tags": "auto-detect,header-based,organize,relabel",
|
||||||
"title": "Auto Rename PDF File",
|
"title": "Auto Rename PDF File",
|
||||||
"desc": "Auto renames a PDF file based on its detected header"
|
"desc": "Auto renames a PDF file based on its detected header"
|
||||||
},
|
},
|
||||||
"adjustContrast": {
|
"adjustContrast": {
|
||||||
|
"tags": "contrast,brightness,saturation",
|
||||||
"title": "Adjust Colors/Contrast",
|
"title": "Adjust Colors/Contrast",
|
||||||
"desc": "Adjust Contrast, Saturation and Brightness of a PDF"
|
"desc": "Adjust Contrast, Saturation and Brightness of a PDF"
|
||||||
},
|
},
|
||||||
"crop": {
|
"crop": {
|
||||||
|
"tags": "trim,cut,resize",
|
||||||
"title": "Crop PDF",
|
"title": "Crop PDF",
|
||||||
"desc": "Crop a PDF to reduce its size (maintains text!)"
|
"desc": "Crop a PDF to reduce its size (maintains text!)"
|
||||||
},
|
},
|
||||||
"autoSplitPDF": {
|
"autoSplitPDF": {
|
||||||
|
"tags": "auto,split,QR",
|
||||||
"title": "Auto Split Pages",
|
"title": "Auto Split Pages",
|
||||||
"desc": "Auto Split Scanned PDF with physical scanned page splitter QR Code"
|
"desc": "Auto Split Scanned PDF with physical scanned page splitter QR Code"
|
||||||
},
|
},
|
||||||
"sanitizePDF": {
|
"sanitizePDF": {
|
||||||
|
"tags": "clean,purge,remove",
|
||||||
"title": "Sanitize",
|
"title": "Sanitize",
|
||||||
"desc": "Remove scripts and other elements from PDF files"
|
"desc": "Remove scripts and other elements from PDF files"
|
||||||
},
|
},
|
||||||
"URLToPDF": {
|
"URLToPDF": {
|
||||||
|
"tags": "convert,url,website",
|
||||||
"title": "URL/Website To PDF",
|
"title": "URL/Website To PDF",
|
||||||
"desc": "Converts any http(s)URL to PDF"
|
"desc": "Converts any http(s)URL to PDF"
|
||||||
},
|
},
|
||||||
"HTMLToPDF": {
|
"HTMLToPDF": {
|
||||||
|
"tags": "convert,html,web",
|
||||||
"title": "HTML to PDF",
|
"title": "HTML to PDF",
|
||||||
"desc": "Converts any HTML file or zip to PDF"
|
"desc": "Converts any HTML file or zip to PDF"
|
||||||
},
|
},
|
||||||
"MarkdownToPDF": {
|
"MarkdownToPDF": {
|
||||||
|
"tags": "convert,markdown,md",
|
||||||
"title": "Markdown to PDF",
|
"title": "Markdown to PDF",
|
||||||
"desc": "Converts any Markdown file to PDF"
|
"desc": "Converts any Markdown file to PDF"
|
||||||
},
|
},
|
||||||
"PDFToMarkdown": {
|
"PDFToMarkdown": {
|
||||||
|
"tags": "convert,markdown,md",
|
||||||
"title": "PDF to Markdown",
|
"title": "PDF to Markdown",
|
||||||
"desc": "Converts any PDF to Markdown"
|
"desc": "Converts any PDF to Markdown"
|
||||||
},
|
},
|
||||||
"getPdfInfo": {
|
"getPdfInfo": {
|
||||||
|
"tags": "info,metadata,details",
|
||||||
"title": "Get ALL Info on PDF",
|
"title": "Get ALL Info on PDF",
|
||||||
"desc": "Grabs any and all information possible on PDFs"
|
"desc": "Grabs any and all information possible on PDFs"
|
||||||
},
|
},
|
||||||
@ -564,50 +615,62 @@
|
|||||||
"desc": "Searches and displays any JS injected into a PDF"
|
"desc": "Searches and displays any JS injected into a PDF"
|
||||||
},
|
},
|
||||||
"autoRedact": {
|
"autoRedact": {
|
||||||
|
"tags": "auto,redact,censor",
|
||||||
"title": "Auto Redact",
|
"title": "Auto Redact",
|
||||||
"desc": "Auto Redacts(Blacks out) text in a PDF based on input text"
|
"desc": "Auto Redacts(Blacks out) text in a PDF based on input text"
|
||||||
},
|
},
|
||||||
"redact": {
|
"redact": {
|
||||||
|
"tags": "censor,blackout,hide",
|
||||||
"title": "Manual Redaction",
|
"title": "Manual Redaction",
|
||||||
"desc": "Redacts a PDF based on selected text, drawn shapes and/or selected page(s)"
|
"desc": "Redacts a PDF based on selected text, drawn shapes and/or selected page(s)"
|
||||||
},
|
},
|
||||||
"PDFToCSV": {
|
"PDFToCSV": {
|
||||||
|
"tags": "convert,csv,table",
|
||||||
"title": "PDF to CSV",
|
"title": "PDF to CSV",
|
||||||
"desc": "Extracts Tables from a PDF converting it to CSV"
|
"desc": "Extracts Tables from a PDF converting it to CSV"
|
||||||
},
|
},
|
||||||
"split-by-size-or-count": {
|
"split-by-size-or-count": {
|
||||||
|
"tags": "auto,split,size",
|
||||||
"title": "Auto Split by Size/Count",
|
"title": "Auto Split by Size/Count",
|
||||||
"desc": "Split a single PDF into multiple documents based on size, page count, or document count"
|
"desc": "Split a single PDF into multiple documents based on size, page count, or document count"
|
||||||
},
|
},
|
||||||
"overlay-pdfs": {
|
"overlay-pdfs": {
|
||||||
|
"tags": "overlay,combine,stack",
|
||||||
"title": "Overlay PDFs",
|
"title": "Overlay PDFs",
|
||||||
"desc": "Overlays PDFs on-top of another PDF"
|
"desc": "Overlays PDFs on-top of another PDF"
|
||||||
},
|
},
|
||||||
"split-by-sections": {
|
"split-by-sections": {
|
||||||
|
"tags": "split,sections,divide",
|
||||||
"title": "Split PDF by Sections",
|
"title": "Split PDF by Sections",
|
||||||
"desc": "Divide each page of a PDF into smaller horizontal and vertical sections"
|
"desc": "Divide each page of a PDF into smaller horizontal and vertical sections"
|
||||||
},
|
},
|
||||||
"AddStampRequest": {
|
"AddStampRequest": {
|
||||||
|
"tags": "stamp,mark,seal",
|
||||||
"title": "Add Stamp to PDF",
|
"title": "Add Stamp to PDF",
|
||||||
"desc": "Add text or add image stamps at set locations"
|
"desc": "Add text or add image stamps at set locations"
|
||||||
},
|
},
|
||||||
"removeImage": {
|
"removeImage": {
|
||||||
|
"tags": "remove,delete,clean",
|
||||||
"title": "Remove image",
|
"title": "Remove image",
|
||||||
"desc": "Remove image from PDF to reduce file size"
|
"desc": "Remove image from PDF to reduce file size"
|
||||||
},
|
},
|
||||||
"splitByChapters": {
|
"splitByChapters": {
|
||||||
|
"tags": "split,chapters,structure",
|
||||||
"title": "Split PDF by Chapters",
|
"title": "Split PDF by Chapters",
|
||||||
"desc": "Split a PDF into multiple files based on its chapter structure."
|
"desc": "Split a PDF into multiple files based on its chapter structure."
|
||||||
},
|
},
|
||||||
"validateSignature": {
|
"validateSignature": {
|
||||||
|
"tags": "validate,verify,certificate",
|
||||||
"title": "Validate PDF Signature",
|
"title": "Validate PDF Signature",
|
||||||
"desc": "Verify digital signatures and certificates in PDF documents"
|
"desc": "Verify digital signatures and certificates in PDF documents"
|
||||||
},
|
},
|
||||||
"swagger": {
|
"swagger": {
|
||||||
|
"tags": "API,documentation,test",
|
||||||
"title": "API Documentation",
|
"title": "API Documentation",
|
||||||
"desc": "View API documentation and test endpoints"
|
"desc": "View API documentation and test endpoints"
|
||||||
},
|
},
|
||||||
"replace-color": {
|
"replace-color": {
|
||||||
|
"tags": "color,replace,invert",
|
||||||
"title": "Replace and Invert Color",
|
"title": "Replace and Invert Color",
|
||||||
"desc": "Replace color for text and background in PDF and invert full color of pdf to reduce file size"
|
"desc": "Replace color for text and background in PDF and invert full color of pdf to reduce file size"
|
||||||
}
|
}
|
||||||
@ -1064,7 +1127,6 @@
|
|||||||
"info": "Python is not installed. It is required to run."
|
"info": "Python is not installed. It is required to run."
|
||||||
},
|
},
|
||||||
"sign": {
|
"sign": {
|
||||||
"tags": "authorize,initials,drawn-signature,text-sign,image-signature",
|
|
||||||
"title": "Sign",
|
"title": "Sign",
|
||||||
"header": "Sign PDFs",
|
"header": "Sign PDFs",
|
||||||
"upload": "Upload Image",
|
"upload": "Upload Image",
|
||||||
|
@ -9,13 +9,22 @@ import NoToolsFound from './shared/NoToolsFound';
|
|||||||
import "./toolPicker/ToolPicker.css";
|
import "./toolPicker/ToolPicker.css";
|
||||||
|
|
||||||
interface SearchResultsProps {
|
interface SearchResultsProps {
|
||||||
filteredTools: [string, ToolRegistryEntry][];
|
filteredTools: Array<{ item: [string, ToolRegistryEntry]; matchedText?: string }>;
|
||||||
onSelect: (id: string) => void;
|
onSelect: (id: string) => void;
|
||||||
|
searchQuery?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SearchResults: React.FC<SearchResultsProps> = ({ filteredTools, onSelect }) => {
|
const SearchResults: React.FC<SearchResultsProps> = ({ filteredTools, onSelect, searchQuery }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { searchGroups } = useToolSections(filteredTools);
|
const { searchGroups } = useToolSections(filteredTools, searchQuery);
|
||||||
|
|
||||||
|
// Create a map of matched text for quick lookup
|
||||||
|
const matchedTextMap = new Map<string, string>();
|
||||||
|
if (filteredTools && Array.isArray(filteredTools)) {
|
||||||
|
filteredTools.forEach(({ item: [id], matchedText }) => {
|
||||||
|
if (matchedText) matchedTextMap.set(id, matchedText);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (searchGroups.length === 0) {
|
if (searchGroups.length === 0) {
|
||||||
return <NoToolsFound />;
|
return <NoToolsFound />;
|
||||||
@ -28,15 +37,27 @@ const SearchResults: React.FC<SearchResultsProps> = ({ filteredTools, onSelect }
|
|||||||
<Box key={group.subcategoryId} w="100%">
|
<Box key={group.subcategoryId} w="100%">
|
||||||
<SubcategoryHeader label={getSubcategoryLabel(t, group.subcategoryId)} />
|
<SubcategoryHeader label={getSubcategoryLabel(t, group.subcategoryId)} />
|
||||||
<Stack gap="xs">
|
<Stack gap="xs">
|
||||||
{group.tools.map(({ id, tool }) => (
|
{group.tools.map(({ id, tool }) => {
|
||||||
|
const matchedText = matchedTextMap.get(id);
|
||||||
|
// Check if the match was from synonyms and show the actual synonym that matched
|
||||||
|
const isSynonymMatch = matchedText && tool.synonyms?.some(synonym =>
|
||||||
|
matchedText.toLowerCase().includes(synonym.toLowerCase())
|
||||||
|
);
|
||||||
|
const matchedSynonym = isSynonymMatch ? tool.synonyms?.find(synonym =>
|
||||||
|
matchedText.toLowerCase().includes(synonym.toLowerCase())
|
||||||
|
) : undefined;
|
||||||
|
|
||||||
|
return (
|
||||||
<ToolButton
|
<ToolButton
|
||||||
key={id}
|
key={id}
|
||||||
id={id}
|
id={id}
|
||||||
tool={tool}
|
tool={tool}
|
||||||
isSelected={false}
|
isSelected={false}
|
||||||
onSelect={onSelect}
|
onSelect={onSelect}
|
||||||
|
matchedSynonym={matchedSynonym}
|
||||||
/>
|
/>
|
||||||
))}
|
);
|
||||||
|
})}
|
||||||
</Stack>
|
</Stack>
|
||||||
</Box>
|
</Box>
|
||||||
))}
|
))}
|
||||||
|
@ -72,6 +72,7 @@ export default function ToolPanel() {
|
|||||||
<SearchResults
|
<SearchResults
|
||||||
filteredTools={filteredTools}
|
filteredTools={filteredTools}
|
||||||
onSelect={handleToolSelect}
|
onSelect={handleToolSelect}
|
||||||
|
searchQuery={searchQuery}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : leftPanelView === 'toolPicker' ? (
|
) : leftPanelView === 'toolPicker' ? (
|
||||||
|
@ -10,7 +10,7 @@ import { renderToolButtons } from "./shared/renderToolButtons";
|
|||||||
interface ToolPickerProps {
|
interface ToolPickerProps {
|
||||||
selectedToolKey: string | null;
|
selectedToolKey: string | null;
|
||||||
onSelect: (id: string) => void;
|
onSelect: (id: string) => void;
|
||||||
filteredTools: [string, ToolRegistryEntry][];
|
filteredTools: Array<{ item: [string, ToolRegistryEntry]; matchedText?: string }>;
|
||||||
isSearching?: boolean;
|
isSearching?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,8 +58,13 @@ export default function ToolSelector({
|
|||||||
return registry;
|
return registry;
|
||||||
}, [baseFilteredTools]);
|
}, [baseFilteredTools]);
|
||||||
|
|
||||||
|
// Transform filteredTools to the expected format for useToolSections
|
||||||
|
const transformedFilteredTools = useMemo(() => {
|
||||||
|
return filteredTools.map(([id, tool]) => ({ item: [id, tool] as [string, ToolRegistryEntry] }));
|
||||||
|
}, [filteredTools]);
|
||||||
|
|
||||||
// Use the same tool sections logic as the main ToolPicker
|
// Use the same tool sections logic as the main ToolPicker
|
||||||
const { sections, searchGroups } = useToolSections(filteredTools);
|
const { sections, searchGroups } = useToolSections(transformedFilteredTools);
|
||||||
|
|
||||||
// Determine what to display: search results or organized sections
|
// Determine what to display: search results or organized sections
|
||||||
const isSearching = searchTerm.trim().length > 0;
|
const isSearching = searchTerm.trim().length > 0;
|
||||||
|
@ -13,14 +13,27 @@ export const renderToolButtons = (
|
|||||||
selectedToolKey: string | null,
|
selectedToolKey: string | null,
|
||||||
onSelect: (id: string) => void,
|
onSelect: (id: string) => void,
|
||||||
showSubcategoryHeader: boolean = true,
|
showSubcategoryHeader: boolean = true,
|
||||||
disableNavigation: boolean = false
|
disableNavigation: boolean = false,
|
||||||
) => (
|
searchResults?: Array<{ item: [string, any]; matchedText?: string }>
|
||||||
|
) => {
|
||||||
|
// Create a map of matched text for quick lookup
|
||||||
|
const matchedTextMap = new Map<string, string>();
|
||||||
|
if (searchResults) {
|
||||||
|
searchResults.forEach(({ item: [id], matchedText }) => {
|
||||||
|
if (matchedText) matchedTextMap.set(id, matchedText);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
<Box key={subcategory.subcategoryId} w="100%">
|
<Box key={subcategory.subcategoryId} w="100%">
|
||||||
{showSubcategoryHeader && (
|
{showSubcategoryHeader && (
|
||||||
<SubcategoryHeader label={getSubcategoryLabel(t, subcategory.subcategoryId)} />
|
<SubcategoryHeader label={getSubcategoryLabel(t, subcategory.subcategoryId)} />
|
||||||
)}
|
)}
|
||||||
<div>
|
<div>
|
||||||
{subcategory.tools.map(({ id, tool }) => (
|
{subcategory.tools.map(({ id, tool }) => {
|
||||||
|
const matchedSynonym = matchedTextMap.get(id);
|
||||||
|
|
||||||
|
return (
|
||||||
<ToolButton
|
<ToolButton
|
||||||
key={id}
|
key={id}
|
||||||
id={id}
|
id={id}
|
||||||
@ -28,8 +41,11 @@ export const renderToolButtons = (
|
|||||||
isSelected={selectedToolKey === id}
|
isSelected={selectedToolKey === id}
|
||||||
onSelect={onSelect}
|
onSelect={onSelect}
|
||||||
disableNavigation={disableNavigation}
|
disableNavigation={disableNavigation}
|
||||||
|
matchedSynonym={matchedSynonym}
|
||||||
/>
|
/>
|
||||||
))}
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
@ -13,9 +13,10 @@ interface ToolButtonProps {
|
|||||||
onSelect: (id: string) => void;
|
onSelect: (id: string) => void;
|
||||||
rounded?: boolean;
|
rounded?: boolean;
|
||||||
disableNavigation?: boolean;
|
disableNavigation?: boolean;
|
||||||
|
matchedSynonym?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ToolButton: React.FC<ToolButtonProps> = ({ id, tool, isSelected, onSelect, disableNavigation = false }) => {
|
const ToolButton: React.FC<ToolButtonProps> = ({ id, tool, isSelected, onSelect, disableNavigation = false, matchedSynonym }) => {
|
||||||
const isUnavailable = !tool.component && !tool.link;
|
const isUnavailable = !tool.component && !tool.link;
|
||||||
const { getToolNavigation } = useToolNavigation();
|
const { getToolNavigation } = useToolNavigation();
|
||||||
|
|
||||||
@ -40,6 +41,7 @@ const ToolButton: React.FC<ToolButtonProps> = ({ id, tool, isSelected, onSelect,
|
|||||||
const buttonContent = (
|
const buttonContent = (
|
||||||
<>
|
<>
|
||||||
<div className="tool-button-icon" style={{ color: "var(--tools-text-and-icon-color)", marginRight: "0.5rem", transform: "scale(0.8)", transformOrigin: "center", opacity: isUnavailable ? 0.25 : 1 }}>{tool.icon}</div>
|
<div className="tool-button-icon" style={{ color: "var(--tools-text-and-icon-color)", marginRight: "0.5rem", transform: "scale(0.8)", transformOrigin: "center", opacity: isUnavailable ? 0.25 : 1 }}>{tool.icon}</div>
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-start', flex: 1, overflow: 'visible' }}>
|
||||||
<FitText
|
<FitText
|
||||||
text={tool.name}
|
text={tool.name}
|
||||||
lines={1}
|
lines={1}
|
||||||
@ -47,6 +49,19 @@ const ToolButton: React.FC<ToolButtonProps> = ({ id, tool, isSelected, onSelect,
|
|||||||
as="span"
|
as="span"
|
||||||
style={{ display: 'inline-block', maxWidth: '100%', opacity: isUnavailable ? 0.25 : 1 }}
|
style={{ display: 'inline-block', maxWidth: '100%', opacity: isUnavailable ? 0.25 : 1 }}
|
||||||
/>
|
/>
|
||||||
|
{matchedSynonym && (
|
||||||
|
<span style={{
|
||||||
|
fontSize: '0.75rem',
|
||||||
|
color: 'var(--mantine-color-dimmed)',
|
||||||
|
opacity: isUnavailable ? 0.25 : 1,
|
||||||
|
marginTop: '1px',
|
||||||
|
overflow: 'visible',
|
||||||
|
whiteSpace: 'nowrap'
|
||||||
|
}}>
|
||||||
|
{matchedSynonym}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -66,7 +81,10 @@ const ToolButton: React.FC<ToolButtonProps> = ({ id, tool, isSelected, onSelect,
|
|||||||
fullWidth
|
fullWidth
|
||||||
justify="flex-start"
|
justify="flex-start"
|
||||||
className="tool-button"
|
className="tool-button"
|
||||||
styles={{ root: { borderRadius: 0, color: "var(--tools-text-and-icon-color)" } }}
|
styles={{
|
||||||
|
root: { borderRadius: 0, color: "var(--tools-text-and-icon-color)", overflow: 'visible' },
|
||||||
|
label: { overflow: 'visible' }
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{buttonContent}
|
{buttonContent}
|
||||||
</Button>
|
</Button>
|
||||||
@ -84,7 +102,10 @@ const ToolButton: React.FC<ToolButtonProps> = ({ id, tool, isSelected, onSelect,
|
|||||||
fullWidth
|
fullWidth
|
||||||
justify="flex-start"
|
justify="flex-start"
|
||||||
className="tool-button"
|
className="tool-button"
|
||||||
styles={{ root: { borderRadius: 0, color: "var(--tools-text-and-icon-color)" } }}
|
styles={{
|
||||||
|
root: { borderRadius: 0, color: "var(--tools-text-and-icon-color)", overflow: 'visible' },
|
||||||
|
label: { overflow: 'visible' }
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{buttonContent}
|
{buttonContent}
|
||||||
</Button>
|
</Button>
|
||||||
@ -99,7 +120,7 @@ const ToolButton: React.FC<ToolButtonProps> = ({ id, tool, isSelected, onSelect,
|
|||||||
justify="flex-start"
|
justify="flex-start"
|
||||||
className="tool-button"
|
className="tool-button"
|
||||||
aria-disabled={isUnavailable}
|
aria-disabled={isUnavailable}
|
||||||
styles={{ root: { borderRadius: 0, color: "var(--tools-text-and-icon-color)", cursor: isUnavailable ? 'not-allowed' : undefined } }}
|
styles={{ root: { borderRadius: 0, color: "var(--tools-text-and-icon-color)", cursor: isUnavailable ? 'not-allowed' : undefined, overflow: 'visible' }, label: { overflow: 'visible' } }}
|
||||||
>
|
>
|
||||||
{buttonContent}
|
{buttonContent}
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -5,6 +5,7 @@ import LocalIcon from '../../shared/LocalIcon';
|
|||||||
import { ToolRegistryEntry } from "../../../data/toolsTaxonomy";
|
import { ToolRegistryEntry } from "../../../data/toolsTaxonomy";
|
||||||
import { TextInput } from "../../shared/TextInput";
|
import { TextInput } from "../../shared/TextInput";
|
||||||
import "./ToolPicker.css";
|
import "./ToolPicker.css";
|
||||||
|
import { rankByFuzzy, idToWords } from "../../../utils/fuzzySearch";
|
||||||
|
|
||||||
interface ToolSearchProps {
|
interface ToolSearchProps {
|
||||||
value: string;
|
value: string;
|
||||||
@ -38,15 +39,14 @@ const ToolSearch = ({
|
|||||||
|
|
||||||
const filteredTools = useMemo(() => {
|
const filteredTools = useMemo(() => {
|
||||||
if (!value.trim()) return [];
|
if (!value.trim()) return [];
|
||||||
return Object.entries(toolRegistry)
|
const entries = Object.entries(toolRegistry).filter(([id]) => !(mode === "dropdown" && id === selectedToolKey));
|
||||||
.filter(([id, tool]) => {
|
const ranked = rankByFuzzy(entries, value, [
|
||||||
if (mode === "dropdown" && id === selectedToolKey) return false;
|
([key]) => idToWords(key),
|
||||||
return (
|
([, v]) => v.name,
|
||||||
tool.name.toLowerCase().includes(value.toLowerCase()) || tool.description.toLowerCase().includes(value.toLowerCase())
|
([, v]) => v.description,
|
||||||
);
|
([, v]) => v.synonyms?.join(' ') || '',
|
||||||
})
|
]).slice(0, 6);
|
||||||
.slice(0, 6)
|
return ranked.map(({ item: [id, tool] }) => ({ id, tool }));
|
||||||
.map(([id, tool]) => ({ id, tool }));
|
|
||||||
}, [value, toolRegistry, mode, selectedToolKey]);
|
}, [value, toolRegistry, mode, selectedToolKey]);
|
||||||
|
|
||||||
const handleSearchChange = (searchValue: string) => {
|
const handleSearchChange = (searchValue: string) => {
|
||||||
|
@ -11,6 +11,7 @@ import { useNavigationActions, useNavigationState } from './NavigationContext';
|
|||||||
import { ToolId, isValidToolId } from '../types/toolId';
|
import { ToolId, isValidToolId } from '../types/toolId';
|
||||||
import { useNavigationUrlSync } from '../hooks/useUrlSync';
|
import { useNavigationUrlSync } from '../hooks/useUrlSync';
|
||||||
import { getDefaultWorkbench } from '../types/workbench';
|
import { getDefaultWorkbench } from '../types/workbench';
|
||||||
|
import { filterToolRegistryByQuery } from '../utils/toolSearch';
|
||||||
|
|
||||||
// State interface
|
// State interface
|
||||||
interface ToolWorkflowState {
|
interface ToolWorkflowState {
|
||||||
@ -100,7 +101,7 @@ interface ToolWorkflowContextValue extends ToolWorkflowState {
|
|||||||
handleReaderToggle: () => void;
|
handleReaderToggle: () => void;
|
||||||
|
|
||||||
// Computed values
|
// Computed values
|
||||||
filteredTools: [string, ToolRegistryEntry][]; // Filtered by search
|
filteredTools: Array<{ item: [string, ToolRegistryEntry]; matchedText?: string }>; // Filtered by search
|
||||||
isPanelVisible: boolean;
|
isPanelVisible: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -219,12 +220,10 @@ export function ToolWorkflowProvider({ children }: ToolWorkflowProviderProps) {
|
|||||||
setReaderMode(true);
|
setReaderMode(true);
|
||||||
}, [setReaderMode]);
|
}, [setReaderMode]);
|
||||||
|
|
||||||
// Filter tools based on search query
|
// Filter tools based on search query with fuzzy matching (name, description, id, synonyms)
|
||||||
const filteredTools = useMemo(() => {
|
const filteredTools = useMemo(() => {
|
||||||
if (!toolRegistry) return [];
|
if (!toolRegistry) return [];
|
||||||
return Object.entries(toolRegistry).filter(([_, { name }]) =>
|
return filterToolRegistryByQuery(toolRegistry as Record<string, ToolRegistryEntry>, state.searchQuery);
|
||||||
name.toLowerCase().includes(state.searchQuery.toLowerCase())
|
|
||||||
);
|
|
||||||
}, [toolRegistry, state.searchQuery]);
|
}, [toolRegistry, state.searchQuery]);
|
||||||
|
|
||||||
const isPanelVisible = useMemo(() =>
|
const isPanelVisible = useMemo(() =>
|
||||||
|
@ -45,6 +45,8 @@ export type ToolRegistryEntry = {
|
|||||||
operationConfig?: ToolOperationConfig<any>;
|
operationConfig?: ToolOperationConfig<any>;
|
||||||
// Settings component for automation configuration
|
// Settings component for automation configuration
|
||||||
settingsComponent?: React.ComponentType<any>;
|
settingsComponent?: React.ComponentType<any>;
|
||||||
|
// Synonyms for search (optional)
|
||||||
|
synonyms?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ToolRegistry = Record<ToolId, ToolRegistryEntry>;
|
export type ToolRegistry = Record<ToolId, ToolRegistryEntry>;
|
||||||
|
@ -12,6 +12,7 @@ import RemoveBlanks from "../tools/RemoveBlanks";
|
|||||||
import RemovePages from "../tools/RemovePages";
|
import RemovePages from "../tools/RemovePages";
|
||||||
import RemovePassword from "../tools/RemovePassword";
|
import RemovePassword from "../tools/RemovePassword";
|
||||||
import { SubcategoryId, ToolCategoryId, ToolRegistry } from "./toolsTaxonomy";
|
import { SubcategoryId, ToolCategoryId, ToolRegistry } from "./toolsTaxonomy";
|
||||||
|
import { getSynonyms } from "../utils/toolSynonyms";
|
||||||
import AddWatermark from "../tools/AddWatermark";
|
import AddWatermark from "../tools/AddWatermark";
|
||||||
import AddStamp from "../tools/AddStamp";
|
import AddStamp from "../tools/AddStamp";
|
||||||
import Merge from '../tools/Merge';
|
import Merge from '../tools/Merge';
|
||||||
@ -172,6 +173,7 @@ export function useFlatToolRegistry(): ToolRegistry {
|
|||||||
description: t("home.certSign.desc", "Sign PDF documents using digital certificates"),
|
description: t("home.certSign.desc", "Sign PDF documents using digital certificates"),
|
||||||
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
||||||
subcategoryId: SubcategoryId.SIGNING,
|
subcategoryId: SubcategoryId.SIGNING,
|
||||||
|
synonyms: getSynonyms(t, "certSign"),
|
||||||
maxFiles: -1,
|
maxFiles: -1,
|
||||||
endpoints: ["cert-sign"],
|
endpoints: ["cert-sign"],
|
||||||
operationConfig: certSignOperationConfig,
|
operationConfig: certSignOperationConfig,
|
||||||
@ -184,6 +186,7 @@ export function useFlatToolRegistry(): ToolRegistry {
|
|||||||
description: t("home.sign.desc", "Adds signature to PDF by drawing, text or image"),
|
description: t("home.sign.desc", "Adds signature to PDF by drawing, text or image"),
|
||||||
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
||||||
subcategoryId: SubcategoryId.SIGNING,
|
subcategoryId: SubcategoryId.SIGNING,
|
||||||
|
synonyms: getSynonyms(t, "sign")
|
||||||
},
|
},
|
||||||
|
|
||||||
// Document Security
|
// Document Security
|
||||||
@ -199,6 +202,7 @@ export function useFlatToolRegistry(): ToolRegistry {
|
|||||||
endpoints: ["add-password"],
|
endpoints: ["add-password"],
|
||||||
operationConfig: addPasswordOperationConfig,
|
operationConfig: addPasswordOperationConfig,
|
||||||
settingsComponent: AddPasswordSettings,
|
settingsComponent: AddPasswordSettings,
|
||||||
|
synonyms: getSynonyms(t, "addPassword")
|
||||||
},
|
},
|
||||||
watermark: {
|
watermark: {
|
||||||
icon: <LocalIcon icon="branding-watermark-outline-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="branding-watermark-outline-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
@ -211,6 +215,7 @@ export function useFlatToolRegistry(): ToolRegistry {
|
|||||||
endpoints: ["add-watermark"],
|
endpoints: ["add-watermark"],
|
||||||
operationConfig: addWatermarkOperationConfig,
|
operationConfig: addWatermarkOperationConfig,
|
||||||
settingsComponent: AddWatermarkSingleStepSettings,
|
settingsComponent: AddWatermarkSingleStepSettings,
|
||||||
|
synonyms: getSynonyms(t, "watermark")
|
||||||
},
|
},
|
||||||
addStamp: {
|
addStamp: {
|
||||||
icon: <LocalIcon icon="approval-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="approval-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
@ -219,6 +224,7 @@ export function useFlatToolRegistry(): ToolRegistry {
|
|||||||
description: t("home.addStamp.desc", "Add text or add image stamps at set locations"),
|
description: t("home.addStamp.desc", "Add text or add image stamps at set locations"),
|
||||||
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
||||||
subcategoryId: SubcategoryId.DOCUMENT_SECURITY,
|
subcategoryId: SubcategoryId.DOCUMENT_SECURITY,
|
||||||
|
synonyms: getSynonyms(t, "addStamp"),
|
||||||
maxFiles: -1,
|
maxFiles: -1,
|
||||||
endpoints: ["add-stamp"],
|
endpoints: ["add-stamp"],
|
||||||
operationConfig: addStampOperationConfig,
|
operationConfig: addStampOperationConfig,
|
||||||
@ -234,6 +240,7 @@ export function useFlatToolRegistry(): ToolRegistry {
|
|||||||
endpoints: ["sanitize-pdf"],
|
endpoints: ["sanitize-pdf"],
|
||||||
operationConfig: sanitizeOperationConfig,
|
operationConfig: sanitizeOperationConfig,
|
||||||
settingsComponent: SanitizeSettings,
|
settingsComponent: SanitizeSettings,
|
||||||
|
synonyms: getSynonyms(t, "sanitize")
|
||||||
},
|
},
|
||||||
flatten: {
|
flatten: {
|
||||||
icon: <LocalIcon icon="layers-clear-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="layers-clear-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
@ -246,6 +253,7 @@ export function useFlatToolRegistry(): ToolRegistry {
|
|||||||
endpoints: ["flatten"],
|
endpoints: ["flatten"],
|
||||||
operationConfig: flattenOperationConfig,
|
operationConfig: flattenOperationConfig,
|
||||||
settingsComponent: FlattenSettings,
|
settingsComponent: FlattenSettings,
|
||||||
|
synonyms: getSynonyms(t, "flatten")
|
||||||
},
|
},
|
||||||
unlockPDFForms: {
|
unlockPDFForms: {
|
||||||
icon: <LocalIcon icon="preview-off-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="preview-off-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
@ -258,6 +266,7 @@ export function useFlatToolRegistry(): ToolRegistry {
|
|||||||
endpoints: ["unlock-pdf-forms"],
|
endpoints: ["unlock-pdf-forms"],
|
||||||
operationConfig: unlockPdfFormsOperationConfig,
|
operationConfig: unlockPdfFormsOperationConfig,
|
||||||
settingsComponent: UnlockPdfFormsSettings,
|
settingsComponent: UnlockPdfFormsSettings,
|
||||||
|
synonyms: getSynonyms(t, "unlockPDFForms"),
|
||||||
},
|
},
|
||||||
manageCertificates: {
|
manageCertificates: {
|
||||||
icon: <LocalIcon icon="license-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="license-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
@ -269,6 +278,7 @@ export function useFlatToolRegistry(): ToolRegistry {
|
|||||||
),
|
),
|
||||||
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
||||||
subcategoryId: SubcategoryId.DOCUMENT_SECURITY,
|
subcategoryId: SubcategoryId.DOCUMENT_SECURITY,
|
||||||
|
synonyms: getSynonyms(t, "manageCertificates"),
|
||||||
},
|
},
|
||||||
changePermissions: {
|
changePermissions: {
|
||||||
icon: <LocalIcon icon="lock-outline" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="lock-outline" width="1.5rem" height="1.5rem" />,
|
||||||
@ -281,6 +291,7 @@ export function useFlatToolRegistry(): ToolRegistry {
|
|||||||
endpoints: ["add-password"],
|
endpoints: ["add-password"],
|
||||||
operationConfig: changePermissionsOperationConfig,
|
operationConfig: changePermissionsOperationConfig,
|
||||||
settingsComponent: ChangePermissionsSettings,
|
settingsComponent: ChangePermissionsSettings,
|
||||||
|
synonyms: getSynonyms(t, "changePermissions"),
|
||||||
},
|
},
|
||||||
getPdfInfo: {
|
getPdfInfo: {
|
||||||
icon: <LocalIcon icon="fact-check-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="fact-check-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
@ -289,6 +300,7 @@ export function useFlatToolRegistry(): ToolRegistry {
|
|||||||
description: t("home.getPdfInfo.desc", "Grabs any and all information possible on PDFs"),
|
description: t("home.getPdfInfo.desc", "Grabs any and all information possible on PDFs"),
|
||||||
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
||||||
subcategoryId: SubcategoryId.VERIFICATION,
|
subcategoryId: SubcategoryId.VERIFICATION,
|
||||||
|
synonyms: getSynonyms(t, "getPdfInfo"),
|
||||||
},
|
},
|
||||||
validateSignature: {
|
validateSignature: {
|
||||||
icon: <LocalIcon icon="verified-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="verified-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
@ -297,6 +309,7 @@ export function useFlatToolRegistry(): ToolRegistry {
|
|||||||
description: t("home.validateSignature.desc", "Verify digital signatures and certificates in PDF documents"),
|
description: t("home.validateSignature.desc", "Verify digital signatures and certificates in PDF documents"),
|
||||||
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
||||||
subcategoryId: SubcategoryId.VERIFICATION,
|
subcategoryId: SubcategoryId.VERIFICATION,
|
||||||
|
synonyms: getSynonyms(t, "validateSignature"),
|
||||||
},
|
},
|
||||||
|
|
||||||
// Document Review
|
// Document Review
|
||||||
@ -312,6 +325,7 @@ export function useFlatToolRegistry(): ToolRegistry {
|
|||||||
),
|
),
|
||||||
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
||||||
subcategoryId: SubcategoryId.DOCUMENT_REVIEW,
|
subcategoryId: SubcategoryId.DOCUMENT_REVIEW,
|
||||||
|
synonyms: getSynonyms(t, "read")
|
||||||
},
|
},
|
||||||
changeMetadata: {
|
changeMetadata: {
|
||||||
icon: <LocalIcon icon="assignment-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="assignment-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
@ -324,6 +338,7 @@ export function useFlatToolRegistry(): ToolRegistry {
|
|||||||
endpoints: ["update-metadata"],
|
endpoints: ["update-metadata"],
|
||||||
operationConfig: changeMetadataOperationConfig,
|
operationConfig: changeMetadataOperationConfig,
|
||||||
settingsComponent: ChangeMetadataSingleStep,
|
settingsComponent: ChangeMetadataSingleStep,
|
||||||
|
synonyms: getSynonyms(t, "changeMetadata")
|
||||||
},
|
},
|
||||||
// Page Formatting
|
// Page Formatting
|
||||||
|
|
||||||
@ -350,6 +365,7 @@ export function useFlatToolRegistry(): ToolRegistry {
|
|||||||
endpoints: ["rotate-pdf"],
|
endpoints: ["rotate-pdf"],
|
||||||
operationConfig: rotateOperationConfig,
|
operationConfig: rotateOperationConfig,
|
||||||
settingsComponent: RotateSettings,
|
settingsComponent: RotateSettings,
|
||||||
|
synonyms: getSynonyms(t, "rotate")
|
||||||
},
|
},
|
||||||
split: {
|
split: {
|
||||||
icon: <LocalIcon icon="content-cut-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="content-cut-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
@ -360,6 +376,7 @@ export function useFlatToolRegistry(): ToolRegistry {
|
|||||||
subcategoryId: SubcategoryId.PAGE_FORMATTING,
|
subcategoryId: SubcategoryId.PAGE_FORMATTING,
|
||||||
operationConfig: splitOperationConfig,
|
operationConfig: splitOperationConfig,
|
||||||
settingsComponent: SplitSettings,
|
settingsComponent: SplitSettings,
|
||||||
|
synonyms: getSynonyms(t, "split")
|
||||||
},
|
},
|
||||||
reorganizePages: {
|
reorganizePages: {
|
||||||
icon: <LocalIcon icon="move-down-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="move-down-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
@ -372,6 +389,7 @@ export function useFlatToolRegistry(): ToolRegistry {
|
|||||||
),
|
),
|
||||||
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
||||||
subcategoryId: SubcategoryId.PAGE_FORMATTING,
|
subcategoryId: SubcategoryId.PAGE_FORMATTING,
|
||||||
|
synonyms: getSynonyms(t, "reorganizePages")
|
||||||
},
|
},
|
||||||
scalePages: {
|
scalePages: {
|
||||||
icon: <LocalIcon icon="crop-free-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="crop-free-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
@ -384,6 +402,7 @@ export function useFlatToolRegistry(): ToolRegistry {
|
|||||||
endpoints: ["scale-pages"],
|
endpoints: ["scale-pages"],
|
||||||
operationConfig: adjustPageScaleOperationConfig,
|
operationConfig: adjustPageScaleOperationConfig,
|
||||||
settingsComponent: AdjustPageScaleSettings,
|
settingsComponent: AdjustPageScaleSettings,
|
||||||
|
synonyms: getSynonyms(t, "scalePages")
|
||||||
},
|
},
|
||||||
addPageNumbers: {
|
addPageNumbers: {
|
||||||
icon: <LocalIcon icon="123-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="123-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
@ -393,6 +412,7 @@ export function useFlatToolRegistry(): ToolRegistry {
|
|||||||
description: t("home.addPageNumbers.desc", "Add Page numbers throughout a document in a set location"),
|
description: t("home.addPageNumbers.desc", "Add Page numbers throughout a document in a set location"),
|
||||||
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
||||||
subcategoryId: SubcategoryId.PAGE_FORMATTING,
|
subcategoryId: SubcategoryId.PAGE_FORMATTING,
|
||||||
|
synonyms: getSynonyms(t, "addPageNumbers")
|
||||||
},
|
},
|
||||||
pageLayout: {
|
pageLayout: {
|
||||||
icon: <LocalIcon icon="dashboard-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="dashboard-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
@ -402,6 +422,7 @@ export function useFlatToolRegistry(): ToolRegistry {
|
|||||||
description: t("home.pageLayout.desc", "Merge multiple pages of a PDF document into a single page"),
|
description: t("home.pageLayout.desc", "Merge multiple pages of a PDF document into a single page"),
|
||||||
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
||||||
subcategoryId: SubcategoryId.PAGE_FORMATTING,
|
subcategoryId: SubcategoryId.PAGE_FORMATTING,
|
||||||
|
synonyms: getSynonyms(t, "pageLayout")
|
||||||
},
|
},
|
||||||
bookletImposition: {
|
bookletImposition: {
|
||||||
icon: <LocalIcon icon="menu-book-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="menu-book-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
@ -426,6 +447,7 @@ export function useFlatToolRegistry(): ToolRegistry {
|
|||||||
urlPath: '/pdf-to-single-page',
|
urlPath: '/pdf-to-single-page',
|
||||||
endpoints: ["pdf-to-single-page"],
|
endpoints: ["pdf-to-single-page"],
|
||||||
operationConfig: singleLargePageOperationConfig,
|
operationConfig: singleLargePageOperationConfig,
|
||||||
|
synonyms: getSynonyms(t, "pdfToSinglePage")
|
||||||
},
|
},
|
||||||
addAttachments: {
|
addAttachments: {
|
||||||
icon: <LocalIcon icon="attachment-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="attachment-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
@ -435,6 +457,7 @@ export function useFlatToolRegistry(): ToolRegistry {
|
|||||||
description: t("home.addAttachments.desc", "Add or remove embedded files (attachments) to/from a PDF"),
|
description: t("home.addAttachments.desc", "Add or remove embedded files (attachments) to/from a PDF"),
|
||||||
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
||||||
subcategoryId: SubcategoryId.PAGE_FORMATTING,
|
subcategoryId: SubcategoryId.PAGE_FORMATTING,
|
||||||
|
synonyms: getSynonyms(t, "addAttachments")
|
||||||
},
|
},
|
||||||
|
|
||||||
// Extraction
|
// Extraction
|
||||||
@ -446,6 +469,7 @@ export function useFlatToolRegistry(): ToolRegistry {
|
|||||||
description: t("home.extractPages.desc", "Extract specific pages from a PDF document"),
|
description: t("home.extractPages.desc", "Extract specific pages from a PDF document"),
|
||||||
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
||||||
subcategoryId: SubcategoryId.EXTRACTION,
|
subcategoryId: SubcategoryId.EXTRACTION,
|
||||||
|
synonyms: getSynonyms(t, "extractPages")
|
||||||
},
|
},
|
||||||
extractImages: {
|
extractImages: {
|
||||||
icon: <LocalIcon icon="filter-alt" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="filter-alt" width="1.5rem" height="1.5rem" />,
|
||||||
@ -454,6 +478,7 @@ export function useFlatToolRegistry(): ToolRegistry {
|
|||||||
description: t("home.extractImages.desc", "Extract images from PDF documents"),
|
description: t("home.extractImages.desc", "Extract images from PDF documents"),
|
||||||
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
||||||
subcategoryId: SubcategoryId.EXTRACTION,
|
subcategoryId: SubcategoryId.EXTRACTION,
|
||||||
|
synonyms: getSynonyms(t, "extractImages")
|
||||||
},
|
},
|
||||||
|
|
||||||
// Removal
|
// Removal
|
||||||
@ -467,6 +492,7 @@ export function useFlatToolRegistry(): ToolRegistry {
|
|||||||
subcategoryId: SubcategoryId.REMOVAL,
|
subcategoryId: SubcategoryId.REMOVAL,
|
||||||
maxFiles: 1,
|
maxFiles: 1,
|
||||||
endpoints: ["remove-pages"],
|
endpoints: ["remove-pages"],
|
||||||
|
synonyms: getSynonyms(t, "removePages")
|
||||||
},
|
},
|
||||||
removeBlanks: {
|
removeBlanks: {
|
||||||
icon: <LocalIcon icon="scan-delete-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="scan-delete-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
@ -477,6 +503,7 @@ export function useFlatToolRegistry(): ToolRegistry {
|
|||||||
subcategoryId: SubcategoryId.REMOVAL,
|
subcategoryId: SubcategoryId.REMOVAL,
|
||||||
maxFiles: 1,
|
maxFiles: 1,
|
||||||
endpoints: ["remove-blanks"],
|
endpoints: ["remove-blanks"],
|
||||||
|
synonyms: getSynonyms(t, "removeBlanks")
|
||||||
},
|
},
|
||||||
removeAnnotations: {
|
removeAnnotations: {
|
||||||
icon: <LocalIcon icon="thread-unread-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="thread-unread-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
@ -485,6 +512,7 @@ export function useFlatToolRegistry(): ToolRegistry {
|
|||||||
description: t("home.removeAnnotations.desc", "Remove annotations and comments from PDF documents"),
|
description: t("home.removeAnnotations.desc", "Remove annotations and comments from PDF documents"),
|
||||||
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
||||||
subcategoryId: SubcategoryId.REMOVAL,
|
subcategoryId: SubcategoryId.REMOVAL,
|
||||||
|
synonyms: getSynonyms(t, "removeAnnotations")
|
||||||
},
|
},
|
||||||
removeImage: {
|
removeImage: {
|
||||||
icon: <LocalIcon icon="remove-selection-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="remove-selection-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
@ -493,6 +521,7 @@ export function useFlatToolRegistry(): ToolRegistry {
|
|||||||
description: t("home.removeImage.desc", "Remove images from PDF documents"),
|
description: t("home.removeImage.desc", "Remove images from PDF documents"),
|
||||||
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
categoryId: ToolCategoryId.STANDARD_TOOLS,
|
||||||
subcategoryId: SubcategoryId.REMOVAL,
|
subcategoryId: SubcategoryId.REMOVAL,
|
||||||
|
synonyms: getSynonyms(t, "removeImage"),
|
||||||
},
|
},
|
||||||
removePassword: {
|
removePassword: {
|
||||||
icon: <LocalIcon icon="lock-open-right-outline-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="lock-open-right-outline-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
@ -505,6 +534,7 @@ export function useFlatToolRegistry(): ToolRegistry {
|
|||||||
maxFiles: -1,
|
maxFiles: -1,
|
||||||
operationConfig: removePasswordOperationConfig,
|
operationConfig: removePasswordOperationConfig,
|
||||||
settingsComponent: RemovePasswordSettings,
|
settingsComponent: RemovePasswordSettings,
|
||||||
|
synonyms: getSynonyms(t, "removePassword")
|
||||||
},
|
},
|
||||||
removeCertSign: {
|
removeCertSign: {
|
||||||
icon: <LocalIcon icon="remove-moderator-outline-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="remove-moderator-outline-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
@ -516,6 +546,7 @@ export function useFlatToolRegistry(): ToolRegistry {
|
|||||||
maxFiles: -1,
|
maxFiles: -1,
|
||||||
endpoints: ["remove-certificate-sign"],
|
endpoints: ["remove-certificate-sign"],
|
||||||
operationConfig: removeCertificateSignOperationConfig,
|
operationConfig: removeCertificateSignOperationConfig,
|
||||||
|
synonyms: getSynonyms(t, "removeCertSign"),
|
||||||
},
|
},
|
||||||
|
|
||||||
// Automation
|
// Automation
|
||||||
@ -533,6 +564,7 @@ export function useFlatToolRegistry(): ToolRegistry {
|
|||||||
maxFiles: -1,
|
maxFiles: -1,
|
||||||
supportedFormats: CONVERT_SUPPORTED_FORMATS,
|
supportedFormats: CONVERT_SUPPORTED_FORMATS,
|
||||||
endpoints: ["handleData"],
|
endpoints: ["handleData"],
|
||||||
|
synonyms: getSynonyms(t, "automate"),
|
||||||
},
|
},
|
||||||
autoRename: {
|
autoRename: {
|
||||||
icon: <LocalIcon icon="match-word-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="match-word-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
@ -544,6 +576,7 @@ export function useFlatToolRegistry(): ToolRegistry {
|
|||||||
description: t("home.autoRename.desc", "Automatically rename PDF files based on their content"),
|
description: t("home.autoRename.desc", "Automatically rename PDF files based on their content"),
|
||||||
categoryId: ToolCategoryId.ADVANCED_TOOLS,
|
categoryId: ToolCategoryId.ADVANCED_TOOLS,
|
||||||
subcategoryId: SubcategoryId.AUTOMATION,
|
subcategoryId: SubcategoryId.AUTOMATION,
|
||||||
|
synonyms: getSynonyms(t, "autoRename"),
|
||||||
},
|
},
|
||||||
autoSplitPDF: {
|
autoSplitPDF: {
|
||||||
icon: <LocalIcon icon="split-scene-right-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="split-scene-right-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
@ -552,6 +585,7 @@ export function useFlatToolRegistry(): ToolRegistry {
|
|||||||
description: t("home.autoSplitPDF.desc", "Automatically split PDF pages based on content detection"),
|
description: t("home.autoSplitPDF.desc", "Automatically split PDF pages based on content detection"),
|
||||||
categoryId: ToolCategoryId.ADVANCED_TOOLS,
|
categoryId: ToolCategoryId.ADVANCED_TOOLS,
|
||||||
subcategoryId: SubcategoryId.AUTOMATION,
|
subcategoryId: SubcategoryId.AUTOMATION,
|
||||||
|
synonyms: getSynonyms(t, "autoSplitPDF"),
|
||||||
},
|
},
|
||||||
autoSizeSplitPDF: {
|
autoSizeSplitPDF: {
|
||||||
icon: <LocalIcon icon="content-cut-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="content-cut-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
@ -560,6 +594,7 @@ export function useFlatToolRegistry(): ToolRegistry {
|
|||||||
description: t("home.autoSizeSplitPDF.desc", "Automatically split PDFs by file size or page count"),
|
description: t("home.autoSizeSplitPDF.desc", "Automatically split PDFs by file size or page count"),
|
||||||
categoryId: ToolCategoryId.ADVANCED_TOOLS,
|
categoryId: ToolCategoryId.ADVANCED_TOOLS,
|
||||||
subcategoryId: SubcategoryId.AUTOMATION,
|
subcategoryId: SubcategoryId.AUTOMATION,
|
||||||
|
synonyms: getSynonyms(t, "autoSizeSplitPDF"),
|
||||||
},
|
},
|
||||||
|
|
||||||
// Advanced Formatting
|
// Advanced Formatting
|
||||||
@ -571,6 +606,7 @@ export function useFlatToolRegistry(): ToolRegistry {
|
|||||||
description: t("home.adjustContrast.desc", "Adjust colors and contrast of PDF documents"),
|
description: t("home.adjustContrast.desc", "Adjust colors and contrast of PDF documents"),
|
||||||
categoryId: ToolCategoryId.ADVANCED_TOOLS,
|
categoryId: ToolCategoryId.ADVANCED_TOOLS,
|
||||||
subcategoryId: SubcategoryId.ADVANCED_FORMATTING,
|
subcategoryId: SubcategoryId.ADVANCED_FORMATTING,
|
||||||
|
synonyms: getSynonyms(t, "adjustContrast"),
|
||||||
},
|
},
|
||||||
repair: {
|
repair: {
|
||||||
icon: <LocalIcon icon="build-outline-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="build-outline-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
@ -583,6 +619,7 @@ export function useFlatToolRegistry(): ToolRegistry {
|
|||||||
endpoints: ["repair"],
|
endpoints: ["repair"],
|
||||||
operationConfig: repairOperationConfig,
|
operationConfig: repairOperationConfig,
|
||||||
settingsComponent: RepairSettings,
|
settingsComponent: RepairSettings,
|
||||||
|
synonyms: getSynonyms(t, "repair")
|
||||||
},
|
},
|
||||||
scannerImageSplit: {
|
scannerImageSplit: {
|
||||||
icon: <LocalIcon icon="scanner-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="scanner-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
@ -591,6 +628,7 @@ export function useFlatToolRegistry(): ToolRegistry {
|
|||||||
description: t("home.scannerImageSplit.desc", "Detect and split scanned photos into separate pages"),
|
description: t("home.scannerImageSplit.desc", "Detect and split scanned photos into separate pages"),
|
||||||
categoryId: ToolCategoryId.ADVANCED_TOOLS,
|
categoryId: ToolCategoryId.ADVANCED_TOOLS,
|
||||||
subcategoryId: SubcategoryId.ADVANCED_FORMATTING,
|
subcategoryId: SubcategoryId.ADVANCED_FORMATTING,
|
||||||
|
synonyms: getSynonyms(t, "ScannerImageSplit"),
|
||||||
},
|
},
|
||||||
overlayPdfs: {
|
overlayPdfs: {
|
||||||
icon: <LocalIcon icon="layers-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="layers-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
@ -599,6 +637,7 @@ export function useFlatToolRegistry(): ToolRegistry {
|
|||||||
description: t("home.overlayPdfs.desc", "Overlay one PDF on top of another"),
|
description: t("home.overlayPdfs.desc", "Overlay one PDF on top of another"),
|
||||||
categoryId: ToolCategoryId.ADVANCED_TOOLS,
|
categoryId: ToolCategoryId.ADVANCED_TOOLS,
|
||||||
subcategoryId: SubcategoryId.ADVANCED_FORMATTING,
|
subcategoryId: SubcategoryId.ADVANCED_FORMATTING,
|
||||||
|
synonyms: getSynonyms(t, "overlayPdfs"),
|
||||||
},
|
},
|
||||||
replaceColorPdf: {
|
replaceColorPdf: {
|
||||||
icon: <LocalIcon icon="format-color-fill-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="format-color-fill-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
@ -607,6 +646,7 @@ export function useFlatToolRegistry(): ToolRegistry {
|
|||||||
description: t("home.replaceColorPdf.desc", "Replace or invert colors in PDF documents"),
|
description: t("home.replaceColorPdf.desc", "Replace or invert colors in PDF documents"),
|
||||||
categoryId: ToolCategoryId.ADVANCED_TOOLS,
|
categoryId: ToolCategoryId.ADVANCED_TOOLS,
|
||||||
subcategoryId: SubcategoryId.ADVANCED_FORMATTING,
|
subcategoryId: SubcategoryId.ADVANCED_FORMATTING,
|
||||||
|
synonyms: getSynonyms(t, "replaceColorPdf"),
|
||||||
},
|
},
|
||||||
addImage: {
|
addImage: {
|
||||||
icon: <LocalIcon icon="image-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="image-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
@ -615,6 +655,7 @@ export function useFlatToolRegistry(): ToolRegistry {
|
|||||||
description: t("home.addImage.desc", "Add images to PDF documents"),
|
description: t("home.addImage.desc", "Add images to PDF documents"),
|
||||||
categoryId: ToolCategoryId.ADVANCED_TOOLS,
|
categoryId: ToolCategoryId.ADVANCED_TOOLS,
|
||||||
subcategoryId: SubcategoryId.ADVANCED_FORMATTING,
|
subcategoryId: SubcategoryId.ADVANCED_FORMATTING,
|
||||||
|
synonyms: getSynonyms(t, "addImage"),
|
||||||
},
|
},
|
||||||
editTableOfContents: {
|
editTableOfContents: {
|
||||||
icon: <LocalIcon icon="bookmark-add-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="bookmark-add-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
@ -623,6 +664,7 @@ export function useFlatToolRegistry(): ToolRegistry {
|
|||||||
description: t("home.editTableOfContents.desc", "Add or edit bookmarks and table of contents in PDF documents"),
|
description: t("home.editTableOfContents.desc", "Add or edit bookmarks and table of contents in PDF documents"),
|
||||||
categoryId: ToolCategoryId.ADVANCED_TOOLS,
|
categoryId: ToolCategoryId.ADVANCED_TOOLS,
|
||||||
subcategoryId: SubcategoryId.ADVANCED_FORMATTING,
|
subcategoryId: SubcategoryId.ADVANCED_FORMATTING,
|
||||||
|
synonyms: getSynonyms(t, "editTableOfContents"),
|
||||||
},
|
},
|
||||||
fakeScan: {
|
fakeScan: {
|
||||||
icon: <LocalIcon icon="scanner-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="scanner-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
@ -631,6 +673,7 @@ export function useFlatToolRegistry(): ToolRegistry {
|
|||||||
description: t("home.fakeScan.desc", "Create a PDF that looks like it was scanned"),
|
description: t("home.fakeScan.desc", "Create a PDF that looks like it was scanned"),
|
||||||
categoryId: ToolCategoryId.ADVANCED_TOOLS,
|
categoryId: ToolCategoryId.ADVANCED_TOOLS,
|
||||||
subcategoryId: SubcategoryId.ADVANCED_FORMATTING,
|
subcategoryId: SubcategoryId.ADVANCED_FORMATTING,
|
||||||
|
synonyms: getSynonyms(t, "fakeScan"),
|
||||||
},
|
},
|
||||||
|
|
||||||
// Developer Tools
|
// Developer Tools
|
||||||
@ -642,6 +685,7 @@ export function useFlatToolRegistry(): ToolRegistry {
|
|||||||
description: t("home.showJS.desc", "Extract and display JavaScript code from PDF documents"),
|
description: t("home.showJS.desc", "Extract and display JavaScript code from PDF documents"),
|
||||||
categoryId: ToolCategoryId.ADVANCED_TOOLS,
|
categoryId: ToolCategoryId.ADVANCED_TOOLS,
|
||||||
subcategoryId: SubcategoryId.DEVELOPER_TOOLS,
|
subcategoryId: SubcategoryId.DEVELOPER_TOOLS,
|
||||||
|
synonyms: getSynonyms(t, "showJS"),
|
||||||
},
|
},
|
||||||
devApi: {
|
devApi: {
|
||||||
icon: <LocalIcon icon="open-in-new-rounded" width="1.5rem" height="1.5rem" style={{ color: "#2F7BF6" }} />,
|
icon: <LocalIcon icon="open-in-new-rounded" width="1.5rem" height="1.5rem" style={{ color: "#2F7BF6" }} />,
|
||||||
@ -651,6 +695,7 @@ export function useFlatToolRegistry(): ToolRegistry {
|
|||||||
categoryId: ToolCategoryId.ADVANCED_TOOLS,
|
categoryId: ToolCategoryId.ADVANCED_TOOLS,
|
||||||
subcategoryId: SubcategoryId.DEVELOPER_TOOLS,
|
subcategoryId: SubcategoryId.DEVELOPER_TOOLS,
|
||||||
link: "https://stirlingpdf.io/swagger-ui/5.21.0/index.html",
|
link: "https://stirlingpdf.io/swagger-ui/5.21.0/index.html",
|
||||||
|
synonyms: getSynonyms(t, "devApi"),
|
||||||
},
|
},
|
||||||
devFolderScanning: {
|
devFolderScanning: {
|
||||||
icon: <LocalIcon icon="open-in-new-rounded" width="1.5rem" height="1.5rem" style={{ color: "#2F7BF6" }} />,
|
icon: <LocalIcon icon="open-in-new-rounded" width="1.5rem" height="1.5rem" style={{ color: "#2F7BF6" }} />,
|
||||||
@ -660,6 +705,7 @@ export function useFlatToolRegistry(): ToolRegistry {
|
|||||||
categoryId: ToolCategoryId.ADVANCED_TOOLS,
|
categoryId: ToolCategoryId.ADVANCED_TOOLS,
|
||||||
subcategoryId: SubcategoryId.DEVELOPER_TOOLS,
|
subcategoryId: SubcategoryId.DEVELOPER_TOOLS,
|
||||||
link: "https://docs.stirlingpdf.com/Advanced%20Configuration/Folder%20Scanning/",
|
link: "https://docs.stirlingpdf.com/Advanced%20Configuration/Folder%20Scanning/",
|
||||||
|
synonyms: getSynonyms(t, "devFolderScanning"),
|
||||||
},
|
},
|
||||||
devSsoGuide: {
|
devSsoGuide: {
|
||||||
icon: <LocalIcon icon="open-in-new-rounded" width="1.5rem" height="1.5rem" style={{ color: "#2F7BF6" }} />,
|
icon: <LocalIcon icon="open-in-new-rounded" width="1.5rem" height="1.5rem" style={{ color: "#2F7BF6" }} />,
|
||||||
@ -669,6 +715,7 @@ export function useFlatToolRegistry(): ToolRegistry {
|
|||||||
categoryId: ToolCategoryId.ADVANCED_TOOLS,
|
categoryId: ToolCategoryId.ADVANCED_TOOLS,
|
||||||
subcategoryId: SubcategoryId.DEVELOPER_TOOLS,
|
subcategoryId: SubcategoryId.DEVELOPER_TOOLS,
|
||||||
link: "https://docs.stirlingpdf.com/Advanced%20Configuration/Single%20Sign-On%20Configuration",
|
link: "https://docs.stirlingpdf.com/Advanced%20Configuration/Single%20Sign-On%20Configuration",
|
||||||
|
synonyms: getSynonyms(t, "devSsoGuide"),
|
||||||
},
|
},
|
||||||
devAirgapped: {
|
devAirgapped: {
|
||||||
icon: <LocalIcon icon="open-in-new-rounded" width="1.5rem" height="1.5rem" style={{ color: "#2F7BF6" }} />,
|
icon: <LocalIcon icon="open-in-new-rounded" width="1.5rem" height="1.5rem" style={{ color: "#2F7BF6" }} />,
|
||||||
@ -678,6 +725,7 @@ export function useFlatToolRegistry(): ToolRegistry {
|
|||||||
categoryId: ToolCategoryId.ADVANCED_TOOLS,
|
categoryId: ToolCategoryId.ADVANCED_TOOLS,
|
||||||
subcategoryId: SubcategoryId.DEVELOPER_TOOLS,
|
subcategoryId: SubcategoryId.DEVELOPER_TOOLS,
|
||||||
link: "https://docs.stirlingpdf.com/Pro/#activation",
|
link: "https://docs.stirlingpdf.com/Pro/#activation",
|
||||||
|
synonyms: getSynonyms(t, "devAirgapped"),
|
||||||
},
|
},
|
||||||
|
|
||||||
// Recommended Tools
|
// Recommended Tools
|
||||||
@ -688,6 +736,7 @@ export function useFlatToolRegistry(): ToolRegistry {
|
|||||||
description: t("home.compare.desc", "Compare two PDF documents and highlight differences"),
|
description: t("home.compare.desc", "Compare two PDF documents and highlight differences"),
|
||||||
categoryId: ToolCategoryId.RECOMMENDED_TOOLS,
|
categoryId: ToolCategoryId.RECOMMENDED_TOOLS,
|
||||||
subcategoryId: SubcategoryId.GENERAL,
|
subcategoryId: SubcategoryId.GENERAL,
|
||||||
|
synonyms: getSynonyms(t, "compare")
|
||||||
},
|
},
|
||||||
compress: {
|
compress: {
|
||||||
icon: <LocalIcon icon="zoom-in-map-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="zoom-in-map-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
@ -699,6 +748,7 @@ export function useFlatToolRegistry(): ToolRegistry {
|
|||||||
maxFiles: -1,
|
maxFiles: -1,
|
||||||
operationConfig: compressOperationConfig,
|
operationConfig: compressOperationConfig,
|
||||||
settingsComponent: CompressSettings,
|
settingsComponent: CompressSettings,
|
||||||
|
synonyms: getSynonyms(t, "compress")
|
||||||
},
|
},
|
||||||
convert: {
|
convert: {
|
||||||
icon: <LocalIcon icon="sync-alt-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="sync-alt-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
@ -728,6 +778,7 @@ export function useFlatToolRegistry(): ToolRegistry {
|
|||||||
|
|
||||||
operationConfig: convertOperationConfig,
|
operationConfig: convertOperationConfig,
|
||||||
settingsComponent: ConvertSettings,
|
settingsComponent: ConvertSettings,
|
||||||
|
synonyms: getSynonyms(t, "convert")
|
||||||
},
|
},
|
||||||
merge: {
|
merge: {
|
||||||
icon: <LocalIcon icon="library-add-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="library-add-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
@ -739,7 +790,8 @@ export function useFlatToolRegistry(): ToolRegistry {
|
|||||||
maxFiles: -1,
|
maxFiles: -1,
|
||||||
endpoints: ["merge-pdfs"],
|
endpoints: ["merge-pdfs"],
|
||||||
operationConfig: mergeOperationConfig,
|
operationConfig: mergeOperationConfig,
|
||||||
settingsComponent: MergeSettings
|
settingsComponent: MergeSettings,
|
||||||
|
synonyms: getSynonyms(t, "merge")
|
||||||
},
|
},
|
||||||
multiTool: {
|
multiTool: {
|
||||||
icon: <LocalIcon icon="dashboard-customize-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="dashboard-customize-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
@ -750,6 +802,7 @@ export function useFlatToolRegistry(): ToolRegistry {
|
|||||||
categoryId: ToolCategoryId.RECOMMENDED_TOOLS,
|
categoryId: ToolCategoryId.RECOMMENDED_TOOLS,
|
||||||
subcategoryId: SubcategoryId.GENERAL,
|
subcategoryId: SubcategoryId.GENERAL,
|
||||||
maxFiles: -1,
|
maxFiles: -1,
|
||||||
|
synonyms: getSynonyms(t, "multiTool"),
|
||||||
},
|
},
|
||||||
ocr: {
|
ocr: {
|
||||||
icon: <LocalIcon icon="quick-reference-all-outline-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="quick-reference-all-outline-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
@ -762,6 +815,7 @@ export function useFlatToolRegistry(): ToolRegistry {
|
|||||||
urlPath: '/ocr-pdf',
|
urlPath: '/ocr-pdf',
|
||||||
operationConfig: ocrOperationConfig,
|
operationConfig: ocrOperationConfig,
|
||||||
settingsComponent: OCRSettings,
|
settingsComponent: OCRSettings,
|
||||||
|
synonyms: getSynonyms(t, "ocr")
|
||||||
},
|
},
|
||||||
redact: {
|
redact: {
|
||||||
icon: <LocalIcon icon="visibility-off-rounded" width="1.5rem" height="1.5rem" />,
|
icon: <LocalIcon icon="visibility-off-rounded" width="1.5rem" height="1.5rem" />,
|
||||||
@ -774,6 +828,7 @@ export function useFlatToolRegistry(): ToolRegistry {
|
|||||||
endpoints: ["auto-redact"],
|
endpoints: ["auto-redact"],
|
||||||
operationConfig: redactOperationConfig,
|
operationConfig: redactOperationConfig,
|
||||||
settingsComponent: RedactSingleStepSettings,
|
settingsComponent: RedactSingleStepSettings,
|
||||||
|
synonyms: getSynonyms(t, "redact")
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -27,12 +27,19 @@ export interface ToolSection {
|
|||||||
subcategories: SubcategoryGroup[];
|
subcategories: SubcategoryGroup[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export function useToolSections(filteredTools: [string /* FIX ME: Should be ToolId */, ToolRegistryEntry][]) {
|
export function useToolSections(
|
||||||
|
filteredTools: Array<{ item: [string /* FIX ME: Should be ToolId */, ToolRegistryEntry]; matchedText?: string }>,
|
||||||
|
searchQuery?: string
|
||||||
|
) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const groupedTools = useMemo(() => {
|
const groupedTools = useMemo(() => {
|
||||||
|
if (!filteredTools || !Array.isArray(filteredTools)) {
|
||||||
|
return {} as GroupedTools;
|
||||||
|
}
|
||||||
|
|
||||||
const grouped = {} as GroupedTools;
|
const grouped = {} as GroupedTools;
|
||||||
filteredTools.forEach(([id, tool]) => {
|
filteredTools.forEach(({ item: [id, tool] }) => {
|
||||||
const categoryId = tool.categoryId;
|
const categoryId = tool.categoryId;
|
||||||
const subcategoryId = tool.subcategoryId;
|
const subcategoryId = tool.subcategoryId;
|
||||||
if (!grouped[categoryId]) grouped[categoryId] = {} as SubcategoryIdMap;
|
if (!grouped[categoryId]) grouped[categoryId] = {} as SubcategoryIdMap;
|
||||||
@ -92,9 +99,13 @@ export function useToolSections(filteredTools: [string /* FIX ME: Should be Tool
|
|||||||
}, [groupedTools]);
|
}, [groupedTools]);
|
||||||
|
|
||||||
const searchGroups: SubcategoryGroup[] = useMemo(() => {
|
const searchGroups: SubcategoryGroup[] = useMemo(() => {
|
||||||
|
if (!filteredTools || !Array.isArray(filteredTools)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
const subMap = {} as SubcategoryIdMap;
|
const subMap = {} as SubcategoryIdMap;
|
||||||
const seen = new Set<string /* FIX ME: Should be ToolId */>();
|
const seen = new Set<string /* FIX ME: Should be ToolId */>();
|
||||||
filteredTools.forEach(([id, tool]) => {
|
filteredTools.forEach(({ item: [id, tool] }) => {
|
||||||
const toolId = id as string /* FIX ME: Should be ToolId */;
|
const toolId = id as string /* FIX ME: Should be ToolId */;
|
||||||
if (seen.has(toolId)) return;
|
if (seen.has(toolId)) return;
|
||||||
seen.add(toolId);
|
seen.add(toolId);
|
||||||
@ -102,10 +113,31 @@ export function useToolSections(filteredTools: [string /* FIX ME: Should be Tool
|
|||||||
if (!subMap[sub]) subMap[sub] = [];
|
if (!subMap[sub]) subMap[sub] = [];
|
||||||
subMap[sub].push({ id: toolId, tool });
|
subMap[sub].push({ id: toolId, tool });
|
||||||
});
|
});
|
||||||
return Object.entries(subMap)
|
const entries = Object.entries(subMap);
|
||||||
|
|
||||||
|
// If a search query is present, always order subcategories by first occurrence in
|
||||||
|
// the ranked filteredTools list so the top-ranked tools' subcategory appears first.
|
||||||
|
if (searchQuery && searchQuery.trim()) {
|
||||||
|
const order: SubcategoryId[] = [];
|
||||||
|
filteredTools.forEach(({ item: [_, tool] }) => {
|
||||||
|
const sc = tool.subcategoryId;
|
||||||
|
if (!order.includes(sc)) order.push(sc);
|
||||||
|
});
|
||||||
|
return entries
|
||||||
|
.sort(([a], [b]) => {
|
||||||
|
const ai = order.indexOf(a as SubcategoryId);
|
||||||
|
const bi = order.indexOf(b as SubcategoryId);
|
||||||
|
if (ai !== bi) return ai - bi;
|
||||||
|
return (a as SubcategoryId).localeCompare(b as SubcategoryId);
|
||||||
|
})
|
||||||
|
.map(([subcategoryId, tools]) => ({ subcategoryId, tools } as SubcategoryGroup));
|
||||||
|
}
|
||||||
|
|
||||||
|
// No search: alphabetical subcategory ordering
|
||||||
|
return entries
|
||||||
.sort(([a], [b]) => a.localeCompare(b))
|
.sort(([a], [b]) => a.localeCompare(b))
|
||||||
.map(([subcategoryId, tools]) => ({ subcategoryId, tools } as SubcategoryGroup));
|
.map(([subcategoryId, tools]) => ({ subcategoryId, tools } as SubcategoryGroup));
|
||||||
}, [filteredTools]);
|
}, [filteredTools, searchQuery]);
|
||||||
|
|
||||||
return { sections, searchGroups };
|
return { sections, searchGroups };
|
||||||
}
|
}
|
||||||
|
121
frontend/src/utils/fuzzySearch.ts
Normal file
121
frontend/src/utils/fuzzySearch.ts
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
// Lightweight fuzzy search helpers without external deps
|
||||||
|
// Provides diacritics-insensitive normalization and Levenshtein distance scoring
|
||||||
|
|
||||||
|
function normalizeText(text: string): string {
|
||||||
|
return text
|
||||||
|
.toLowerCase()
|
||||||
|
.normalize('NFD')
|
||||||
|
.replace(/\p{Diacritic}+/gu, '')
|
||||||
|
.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Basic Levenshtein distance (iterative with two rows)
|
||||||
|
function levenshtein(a: string, b: string): number {
|
||||||
|
if (a === b) return 0;
|
||||||
|
const aLen = a.length;
|
||||||
|
const bLen = b.length;
|
||||||
|
if (aLen === 0) return bLen;
|
||||||
|
if (bLen === 0) return aLen;
|
||||||
|
|
||||||
|
const prev = new Array(bLen + 1);
|
||||||
|
const curr = new Array(bLen + 1);
|
||||||
|
|
||||||
|
for (let j = 0; j <= bLen; j++) prev[j] = j;
|
||||||
|
|
||||||
|
for (let i = 1; i <= aLen; i++) {
|
||||||
|
curr[0] = i;
|
||||||
|
const aChar = a.charCodeAt(i - 1);
|
||||||
|
for (let j = 1; j <= bLen; j++) {
|
||||||
|
const cost = aChar === b.charCodeAt(j - 1) ? 0 : 1;
|
||||||
|
curr[j] = Math.min(
|
||||||
|
prev[j] + 1, // deletion
|
||||||
|
curr[j - 1] + 1, // insertion
|
||||||
|
prev[j - 1] + cost // substitution
|
||||||
|
);
|
||||||
|
}
|
||||||
|
for (let j = 0; j <= bLen; j++) prev[j] = curr[j];
|
||||||
|
}
|
||||||
|
return curr[bLen];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute a heuristic match score (higher is better)
|
||||||
|
// 1) Exact/substring hits get high base; 2) otherwise use normalized Levenshtein distance
|
||||||
|
export function scoreMatch(queryRaw: string, targetRaw: string): number {
|
||||||
|
const query = normalizeText(queryRaw);
|
||||||
|
const target = normalizeText(targetRaw);
|
||||||
|
if (!query) return 0;
|
||||||
|
if (target.includes(query)) {
|
||||||
|
// Reward earlier/shorter substring matches
|
||||||
|
const pos = target.indexOf(query);
|
||||||
|
return 100 - pos - Math.max(0, target.length - query.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Token-aware: check each word token too, but require better similarity
|
||||||
|
const tokens = target.split(/[^a-z0-9]+/g).filter(Boolean);
|
||||||
|
for (const token of tokens) {
|
||||||
|
if (token.includes(query)) {
|
||||||
|
// Only give high score if the match is substantial (not just "and" matching)
|
||||||
|
const similarity = query.length / Math.max(query.length, token.length);
|
||||||
|
if (similarity >= 0.6) { // Require at least 60% similarity
|
||||||
|
return 80 - Math.abs(token.length - query.length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const distance = levenshtein(query, target.length > 64 ? target.slice(0, 64) : target);
|
||||||
|
const maxLen = Math.max(query.length, target.length, 1);
|
||||||
|
const similarity = 1 - distance / maxLen; // 0..1
|
||||||
|
return Math.floor(similarity * 60); // scale below substring scores
|
||||||
|
}
|
||||||
|
|
||||||
|
export function minScoreForQuery(query: string): number {
|
||||||
|
const len = normalizeText(query).length;
|
||||||
|
if (len <= 3) return 40;
|
||||||
|
if (len <= 6) return 30;
|
||||||
|
return 25;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decide if a target matches a query based on a threshold
|
||||||
|
export function isFuzzyMatch(query: string, target: string, minScore?: number): boolean {
|
||||||
|
const threshold = typeof minScore === 'number' ? minScore : minScoreForQuery(query);
|
||||||
|
return scoreMatch(query, target) >= threshold;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convenience: rank a list of items by best score across provided getters
|
||||||
|
export function rankByFuzzy<T>(items: T[], query: string, getters: Array<(item: T) => string>, minScore?: number): Array<{ item: T; score: number; matchedText?: string }>{
|
||||||
|
const results: Array<{ item: T; score: number; matchedText?: string }> = [];
|
||||||
|
const threshold = typeof minScore === 'number' ? minScore : minScoreForQuery(query);
|
||||||
|
for (const item of items) {
|
||||||
|
let best = 0;
|
||||||
|
let matchedText = '';
|
||||||
|
for (const get of getters) {
|
||||||
|
const value = get(item);
|
||||||
|
if (!value) continue;
|
||||||
|
const s = scoreMatch(query, value);
|
||||||
|
if (s > best) {
|
||||||
|
best = s;
|
||||||
|
matchedText = value;
|
||||||
|
}
|
||||||
|
if (best >= 95) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (best >= threshold) results.push({ item, score: best, matchedText });
|
||||||
|
}
|
||||||
|
results.sort((a, b) => b.score - a.score);
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function normalizeForSearch(text: string): string {
|
||||||
|
return normalizeText(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert ids like "addPassword", "add-password", "add_password" to words for matching
|
||||||
|
export function idToWords(id: string): string {
|
||||||
|
const spaced = id
|
||||||
|
.replace(/([a-z0-9])([A-Z])/g, '$1 $2')
|
||||||
|
.replace(/[._-]+/g, ' ');
|
||||||
|
return normalizeText(spaced);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
99
frontend/src/utils/toolSearch.ts
Normal file
99
frontend/src/utils/toolSearch.ts
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
import { ToolRegistryEntry } from "../data/toolsTaxonomy";
|
||||||
|
import { scoreMatch, minScoreForQuery, normalizeForSearch } from "./fuzzySearch";
|
||||||
|
|
||||||
|
export interface RankedToolItem {
|
||||||
|
item: [string, ToolRegistryEntry];
|
||||||
|
matchedText?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function filterToolRegistryByQuery(
|
||||||
|
toolRegistry: Record<string, ToolRegistryEntry>,
|
||||||
|
query: string
|
||||||
|
): RankedToolItem[] {
|
||||||
|
const entries = Object.entries(toolRegistry);
|
||||||
|
if (!query.trim()) {
|
||||||
|
return entries.map(([id, tool]) => ({ item: [id, tool] as [string, ToolRegistryEntry] }));
|
||||||
|
}
|
||||||
|
|
||||||
|
const nq = normalizeForSearch(query);
|
||||||
|
const threshold = minScoreForQuery(query);
|
||||||
|
|
||||||
|
const exactName: Array<{ id: string; tool: ToolRegistryEntry; pos: number }> = [];
|
||||||
|
const exactSyn: Array<{ id: string; tool: ToolRegistryEntry; text: string; pos: number }> = [];
|
||||||
|
const fuzzyName: Array<{ id: string; tool: ToolRegistryEntry; score: number; text: string }> = [];
|
||||||
|
const fuzzySyn: Array<{ id: string; tool: ToolRegistryEntry; score: number; text: string }> = [];
|
||||||
|
|
||||||
|
for (const [id, tool] of entries) {
|
||||||
|
const nameNorm = normalizeForSearch(tool.name || '');
|
||||||
|
const pos = nameNorm.indexOf(nq);
|
||||||
|
if (pos !== -1) {
|
||||||
|
exactName.push({ id, tool, pos });
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const syns = Array.isArray(tool.synonyms) ? tool.synonyms : [];
|
||||||
|
let matchedExactSyn: { text: string; pos: number } | null = null;
|
||||||
|
for (const s of syns) {
|
||||||
|
const sn = normalizeForSearch(s);
|
||||||
|
const sp = sn.indexOf(nq);
|
||||||
|
if (sp !== -1) {
|
||||||
|
matchedExactSyn = { text: s, pos: sp };
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (matchedExactSyn) {
|
||||||
|
exactSyn.push({ id, tool, text: matchedExactSyn.text, pos: matchedExactSyn.pos });
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fuzzy name
|
||||||
|
const nameScore = scoreMatch(query, tool.name || '');
|
||||||
|
if (nameScore >= threshold) {
|
||||||
|
fuzzyName.push({ id, tool, score: nameScore, text: tool.name || '' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fuzzy synonyms (we'll consider these only if fuzzy name results are weak)
|
||||||
|
let bestSynScore = 0;
|
||||||
|
let bestSynText = '';
|
||||||
|
for (const s of syns) {
|
||||||
|
const synScore = scoreMatch(query, s);
|
||||||
|
if (synScore > bestSynScore) {
|
||||||
|
bestSynScore = synScore;
|
||||||
|
bestSynText = s;
|
||||||
|
}
|
||||||
|
if (bestSynScore >= 95) break;
|
||||||
|
}
|
||||||
|
if (bestSynScore >= threshold) {
|
||||||
|
fuzzySyn.push({ id, tool, score: bestSynScore, text: bestSynText });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort within buckets
|
||||||
|
exactName.sort((a, b) => a.pos - b.pos || (a.tool.name || '').length - (b.tool.name || '').length);
|
||||||
|
exactSyn.sort((a, b) => a.pos - b.pos || a.text.length - b.text.length);
|
||||||
|
fuzzyName.sort((a, b) => b.score - a.score);
|
||||||
|
fuzzySyn.sort((a, b) => b.score - a.score);
|
||||||
|
|
||||||
|
// Concatenate buckets with de-duplication by tool id
|
||||||
|
const seen = new Set<string>();
|
||||||
|
const ordered: RankedToolItem[] = [];
|
||||||
|
|
||||||
|
const push = (id: string, tool: ToolRegistryEntry, matchedText?: string) => {
|
||||||
|
if (seen.has(id)) return;
|
||||||
|
seen.add(id);
|
||||||
|
ordered.push({ item: [id, tool], matchedText });
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const { id, tool } of exactName) push(id, tool, tool.name);
|
||||||
|
for (const { id, tool, text } of exactSyn) push(id, tool, text);
|
||||||
|
for (const { id, tool, text } of fuzzyName) push(id, tool, text);
|
||||||
|
for (const { id, tool, text } of fuzzySyn) push(id, tool, text);
|
||||||
|
|
||||||
|
if (ordered.length > 0) return ordered;
|
||||||
|
|
||||||
|
// Fallback: return everything unchanged
|
||||||
|
return entries.map(([id, tool]) => ({ item: [id, tool] as [string, ToolRegistryEntry] }));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
24
frontend/src/utils/toolSynonyms.ts
Normal file
24
frontend/src/utils/toolSynonyms.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { TFunction } from 'i18next';
|
||||||
|
|
||||||
|
// Helper function to get synonyms for a tool (only from translations)
|
||||||
|
export const getSynonyms = (t: TFunction, toolId: string): string[] => {
|
||||||
|
try {
|
||||||
|
const tagsKey = `${toolId}.tags`;
|
||||||
|
const tags = t(tagsKey) as unknown as string;
|
||||||
|
|
||||||
|
// If the translation key doesn't exist or returns the key itself, return empty array
|
||||||
|
if (!tags || tags === tagsKey) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Split by comma and clean up the tags
|
||||||
|
return tags
|
||||||
|
.split(',')
|
||||||
|
.map((tag: string) => tag.trim())
|
||||||
|
.filter((tag: string) => tag.length > 0);
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(`Failed to get translated synonyms for tool ${toolId}:`, error);
|
||||||
|
return [];
|
||||||
|
}};
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user