mirror of
https://github.com/alam00000/bentopdf.git
synced 2026-11-05 03:50:29 +01:00
feat: add favorites functionality to tools
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"categories": {
|
||||
"favorites": "المفضلة",
|
||||
"popularTools": "الأدوات الشائعة",
|
||||
"editAnnotate": "تعديل وتعليق",
|
||||
"convertToPdf": "تحويل إلى PDF",
|
||||
@@ -10,6 +11,13 @@
|
||||
"input": "إدخال",
|
||||
"output": "إخراج"
|
||||
},
|
||||
"favorites": {
|
||||
"add": "إضافة إلى المفضلة",
|
||||
"remove": "إزالة من المفضلة",
|
||||
"hintLabel": "تلميح",
|
||||
"hint": "اضغط مطولًا على أي أداة لإضافتها إلى المفضلة.",
|
||||
"hideHint": "إخفاء التلميح"
|
||||
},
|
||||
"convertToPdf": "تحويل إلى PDF",
|
||||
"pdfMultiTool": {
|
||||
"name": "أداة PDF المتعددة",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"categories": {
|
||||
"favorites": "Абранае",
|
||||
"popularTools": "Папулярныя інструменты",
|
||||
"editAnnotate": "Рэдагаванне і анатацыі",
|
||||
"convertToPdf": "Канвертацыя ў PDF",
|
||||
@@ -10,6 +11,13 @@
|
||||
"input": "Увод",
|
||||
"output": "Вывад"
|
||||
},
|
||||
"favorites": {
|
||||
"add": "Дадаць у абранае",
|
||||
"remove": "Выдаліць з абранага",
|
||||
"hintLabel": "Парада",
|
||||
"hint": "Націсніце і ўтрымлівайце інструмент, каб дадаць яго ў абранае.",
|
||||
"hideHint": "Схаваць параду"
|
||||
},
|
||||
"convertToPdf": "Канвертаваць у PDF",
|
||||
"pdfMultiTool": {
|
||||
"name": "Мультыінструмент PDF",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"categories": {
|
||||
"favorites": "Preferits",
|
||||
"popularTools": "Eines populars",
|
||||
"editAnnotate": "Edita i anota",
|
||||
"convertToPdf": "Converteix a PDF",
|
||||
@@ -10,6 +11,13 @@
|
||||
"input": "Entrada",
|
||||
"output": "Sortida"
|
||||
},
|
||||
"favorites": {
|
||||
"add": "Afegeix als preferits",
|
||||
"remove": "Treu dels preferits",
|
||||
"hintLabel": "Consell",
|
||||
"hint": "Mantén premuda una eina per afegir-la als preferits.",
|
||||
"hideHint": "Amaga el consell"
|
||||
},
|
||||
"convertToPdf": "Converteix a PDF",
|
||||
"pdfMultiTool": {
|
||||
"name": "Eina PDF multifunció",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"categories": {
|
||||
"favorites": "Favoritter",
|
||||
"popularTools": "Populære værktøjer",
|
||||
"editAnnotate": "Rediger og annotér",
|
||||
"convertToPdf": "Konverter til PDF",
|
||||
@@ -10,6 +11,13 @@
|
||||
"input": "Input",
|
||||
"output": "Output"
|
||||
},
|
||||
"favorites": {
|
||||
"add": "Føj til favoritter",
|
||||
"remove": "Fjern fra favoritter",
|
||||
"hintLabel": "Tip",
|
||||
"hint": "Tryk og hold på et værktøj for at føje det til dine favoritter.",
|
||||
"hideHint": "Skjul tip"
|
||||
},
|
||||
"convertToPdf": "Konvertér til PDF",
|
||||
"pdfMultiTool": {
|
||||
"name": "PDF Multi-værktøj",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"categories": {
|
||||
"favorites": "Favoriten",
|
||||
"popularTools": "Beliebte Werkzeuge",
|
||||
"editAnnotate": "Bearbeiten & Annotieren",
|
||||
"convertToPdf": "In PDF konvertieren",
|
||||
@@ -10,6 +11,13 @@
|
||||
"input": "Eingabe",
|
||||
"output": "Ausgabe"
|
||||
},
|
||||
"favorites": {
|
||||
"add": "Zu Favoriten hinzufügen",
|
||||
"remove": "Aus Favoriten entfernen",
|
||||
"hintLabel": "Tipp",
|
||||
"hint": "Halte ein Werkzeug gedrückt, um es zu deinen Favoriten hinzuzufügen.",
|
||||
"hideHint": "Tipp ausblenden"
|
||||
},
|
||||
"convertToPdf": "In PDF konvertieren",
|
||||
"pdfMultiTool": {
|
||||
"name": "PDF Multi-Werkzeug",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"categories": {
|
||||
"favorites": "Favorites",
|
||||
"popularTools": "Popular Tools",
|
||||
"editAnnotate": "Edit & Annotate",
|
||||
"convertToPdf": "Convert to PDF",
|
||||
@@ -10,6 +11,13 @@
|
||||
"input": "Input",
|
||||
"output": "Output"
|
||||
},
|
||||
"favorites": {
|
||||
"add": "Add to favorites",
|
||||
"remove": "Remove from favorites",
|
||||
"hintLabel": "Tip",
|
||||
"hint": "Press and hold a tool to add it to your favorites.",
|
||||
"hideHint": "Hide tip"
|
||||
},
|
||||
"convertToPdf": "Convert to PDF",
|
||||
"pdfMultiTool": {
|
||||
"name": "PDF Multi Tool",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"categories": {
|
||||
"favorites": "Favoritos",
|
||||
"popularTools": "Herramientas Populares",
|
||||
"editAnnotate": "Editar y Anotar",
|
||||
"convertToPdf": "Convertir a PDF",
|
||||
@@ -10,6 +11,13 @@
|
||||
"input": "Entrada",
|
||||
"output": "Salida"
|
||||
},
|
||||
"favorites": {
|
||||
"add": "Añadir a favoritos",
|
||||
"remove": "Quitar de favoritos",
|
||||
"hintLabel": "Consejo",
|
||||
"hint": "Mantén pulsada una herramienta para añadirla a tus favoritos.",
|
||||
"hideHint": "Ocultar consejo"
|
||||
},
|
||||
"convertToPdf": "Convertir a PDF",
|
||||
"pdfMultiTool": {
|
||||
"name": "Multiherramienta PDF",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"categories": {
|
||||
"favorites": "علاقهمندیها",
|
||||
"popularTools": "ابزارهای پرکاربرد",
|
||||
"editAnnotate": "ویرایش و حاشیهنویسی",
|
||||
"convertToPdf": "تبدیل به PDF",
|
||||
@@ -10,6 +11,13 @@
|
||||
"input": "ورودی",
|
||||
"output": "خروجی"
|
||||
},
|
||||
"favorites": {
|
||||
"add": "افزودن به علاقهمندیها",
|
||||
"remove": "حذف از علاقهمندیها",
|
||||
"hintLabel": "نکته",
|
||||
"hint": "برای افزودن یک ابزار به علاقهمندیها، آن را لمس کرده و نگه دارید.",
|
||||
"hideHint": "پنهان کردن نکته"
|
||||
},
|
||||
"convertToPdf": "تبدیل به PDF",
|
||||
"pdfMultiTool": {
|
||||
"name": "ابزار چندکارهٔ PDF",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"categories": {
|
||||
"favorites": "Favoris",
|
||||
"popularTools": "Outils populaires",
|
||||
"editAnnotate": "Éditer et annoter",
|
||||
"convertToPdf": "Convertir en PDF",
|
||||
@@ -10,6 +11,13 @@
|
||||
"input": "Entrée",
|
||||
"output": "Sortie"
|
||||
},
|
||||
"favorites": {
|
||||
"add": "Ajouter aux favoris",
|
||||
"remove": "Retirer des favoris",
|
||||
"hintLabel": "Astuce",
|
||||
"hint": "Appuyez longuement sur un outil pour l’ajouter à vos favoris.",
|
||||
"hideHint": "Masquer l’astuce"
|
||||
},
|
||||
"convertToPdf": "Convertir en PDF",
|
||||
"pdfMultiTool": {
|
||||
"name": "Outil PDF tout-en-un",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"categories": {
|
||||
"favorites": "Favorit",
|
||||
"popularTools": "Alat Populer",
|
||||
"editAnnotate": "Edit & Anotasi",
|
||||
"convertToPdf": "Konversi ke PDF",
|
||||
@@ -10,6 +11,13 @@
|
||||
"input": "Masukan",
|
||||
"output": "Keluaran"
|
||||
},
|
||||
"favorites": {
|
||||
"add": "Tambahkan ke favorit",
|
||||
"remove": "Hapus dari favorit",
|
||||
"hintLabel": "Tips",
|
||||
"hint": "Tekan lama sebuah alat untuk menambahkannya ke favorit.",
|
||||
"hideHint": "Sembunyikan tips"
|
||||
},
|
||||
"convertToPdf": "Konversi ke PDF",
|
||||
"pdfMultiTool": {
|
||||
"name": "Alat Multi PDF",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"categories": {
|
||||
"favorites": "Preferiti",
|
||||
"popularTools": "Strumenti popolari",
|
||||
"editAnnotate": "Modifica e Annota",
|
||||
"convertToPdf": "Converti in PDF",
|
||||
@@ -10,6 +11,13 @@
|
||||
"input": "Input",
|
||||
"output": "Output"
|
||||
},
|
||||
"favorites": {
|
||||
"add": "Aggiungi ai preferiti",
|
||||
"remove": "Rimuovi dai preferiti",
|
||||
"hintLabel": "Suggerimento",
|
||||
"hint": "Tieni premuto uno strumento per aggiungerlo ai preferiti.",
|
||||
"hideHint": "Nascondi suggerimento"
|
||||
},
|
||||
"convertToPdf": "Converti in PDF",
|
||||
"pdfMultiTool": {
|
||||
"name": "Strumento PDF multifunzione",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"categories": {
|
||||
"favorites": "お気に入り",
|
||||
"popularTools": "人気のツール",
|
||||
"editAnnotate": "編集と注釈",
|
||||
"convertToPdf": "PDFに変換",
|
||||
@@ -10,6 +11,13 @@
|
||||
"input": "入力",
|
||||
"output": "出力"
|
||||
},
|
||||
"favorites": {
|
||||
"add": "お気に入りに追加",
|
||||
"remove": "お気に入りから削除",
|
||||
"hintLabel": "ヒント",
|
||||
"hint": "ツールを長押しするとお気に入りに追加できます。",
|
||||
"hideHint": "ヒントを非表示"
|
||||
},
|
||||
"convertToPdf": "PDFに変換",
|
||||
"pdfMultiTool": {
|
||||
"name": "PDFマルチツール",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"categories": {
|
||||
"favorites": "즐겨찾기",
|
||||
"popularTools": "인기 도구",
|
||||
"editAnnotate": "편집 및 주석",
|
||||
"convertToPdf": "PDF로 변환",
|
||||
@@ -10,6 +11,13 @@
|
||||
"input": "입력",
|
||||
"output": "출력"
|
||||
},
|
||||
"favorites": {
|
||||
"add": "즐겨찾기에 추가",
|
||||
"remove": "즐겨찾기에서 제거",
|
||||
"hintLabel": "팁",
|
||||
"hint": "도구를 길게 누르면 즐겨찾기에 추가할 수 있습니다.",
|
||||
"hideHint": "팁 숨기기"
|
||||
},
|
||||
"convertToPdf": "PDF로 변환",
|
||||
"pdfMultiTool": {
|
||||
"name": "PDF 멀티 도구",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"categories": {
|
||||
"favorites": "Favorieten",
|
||||
"popularTools": "Populaire Tools",
|
||||
"editAnnotate": "Bewerken & Annoteren",
|
||||
"convertToPdf": "Converteren naar PDF",
|
||||
@@ -10,6 +11,13 @@
|
||||
"input": "Invoer",
|
||||
"output": "Uitvoer"
|
||||
},
|
||||
"favorites": {
|
||||
"add": "Toevoegen aan favorieten",
|
||||
"remove": "Verwijderen uit favorieten",
|
||||
"hintLabel": "Tip",
|
||||
"hint": "Houd een tool ingedrukt om hem aan je favorieten toe te voegen.",
|
||||
"hideHint": "Tip verbergen"
|
||||
},
|
||||
"convertToPdf": "Naar PDF converteren",
|
||||
"pdfMultiTool": {
|
||||
"name": "PDF Multi-tool",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"categories": {
|
||||
"favorites": "Ulubione",
|
||||
"popularTools": "Popularne narzędzia",
|
||||
"editAnnotate": "Edytuj i adnotuj",
|
||||
"convertToPdf": "Konwertuj na PDF",
|
||||
@@ -10,6 +11,13 @@
|
||||
"input": "Wejście",
|
||||
"output": "Wyjście"
|
||||
},
|
||||
"favorites": {
|
||||
"add": "Dodaj do ulubionych",
|
||||
"remove": "Usuń z ulubionych",
|
||||
"hintLabel": "Wskazówka",
|
||||
"hint": "Naciśnij i przytrzymaj narzędzie, aby dodać je do ulubionych.",
|
||||
"hideHint": "Ukryj wskazówkę"
|
||||
},
|
||||
"convertToPdf": "Konwertuj na PDF",
|
||||
"pdfMultiTool": {
|
||||
"name": "Wielofunkcyjne narzędzie PDF",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"categories": {
|
||||
"favorites": "Favoritos",
|
||||
"popularTools": "Ferramentas Populares",
|
||||
"editAnnotate": "Editar e Anotar",
|
||||
"convertToPdf": "Converter para PDF",
|
||||
@@ -10,6 +11,13 @@
|
||||
"input": "Entrada",
|
||||
"output": "Saída"
|
||||
},
|
||||
"favorites": {
|
||||
"add": "Adicionar aos favoritos",
|
||||
"remove": "Remover dos favoritos",
|
||||
"hintLabel": "Dica",
|
||||
"hint": "Toque e segure uma ferramenta para adicioná-la aos favoritos.",
|
||||
"hideHint": "Ocultar dica"
|
||||
},
|
||||
"convertToPdf": "Converter para PDF",
|
||||
"pdfMultiTool": {
|
||||
"name": "Multiferramenta PDF",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"categories": {
|
||||
"favorites": "Избранное",
|
||||
"popularTools": "Популярные инструменты",
|
||||
"editAnnotate": "Редактирование и добавление примечаний",
|
||||
"convertToPdf": "Конвертация в PDF",
|
||||
@@ -10,6 +11,13 @@
|
||||
"input": "Ввод",
|
||||
"output": "Вывод"
|
||||
},
|
||||
"favorites": {
|
||||
"add": "Добавить в избранное",
|
||||
"remove": "Удалить из избранного",
|
||||
"hintLabel": "Совет",
|
||||
"hint": "Нажмите и удерживайте инструмент, чтобы добавить его в избранное.",
|
||||
"hideHint": "Скрыть совет"
|
||||
},
|
||||
"convertToPdf": "Конвертировать в PDF",
|
||||
"pdfMultiTool": {
|
||||
"name": "PDF мультиинструмент",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"categories": {
|
||||
"favorites": "Obľúbené",
|
||||
"popularTools": "Populárne nástroje",
|
||||
"editAnnotate": "Úprava a anotácia",
|
||||
"convertToPdf": "Previesť do PDF",
|
||||
@@ -10,6 +11,13 @@
|
||||
"input": "Vstup",
|
||||
"output": "Výstup"
|
||||
},
|
||||
"favorites": {
|
||||
"add": "Pridať do obľúbených",
|
||||
"remove": "Odstrániť z obľúbených",
|
||||
"hintLabel": "Tip",
|
||||
"hint": "Podržte prst na nástroji a pridajte ho do obľúbených.",
|
||||
"hideHint": "Skryť tip"
|
||||
},
|
||||
"convertToPdf": "Previesť do PDF",
|
||||
"pdfMultiTool": {
|
||||
"name": "PDF Multi Nástroj",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"categories": {
|
||||
"favorites": "Favoriter",
|
||||
"popularTools": "Populära verktyg",
|
||||
"editAnnotate": "Redigera & Kommentera",
|
||||
"convertToPdf": "Konvertera till PDF",
|
||||
@@ -10,6 +11,13 @@
|
||||
"input": "Inmatning",
|
||||
"output": "Utdata"
|
||||
},
|
||||
"favorites": {
|
||||
"add": "Lägg till i favoriter",
|
||||
"remove": "Ta bort från favoriter",
|
||||
"hintLabel": "Tips",
|
||||
"hint": "Tryck och håll på ett verktyg för att lägga till det i dina favoriter.",
|
||||
"hideHint": "Dölj tips"
|
||||
},
|
||||
"convertToPdf": "Konvertera till PDF",
|
||||
"pdfMultiTool": {
|
||||
"name": "PDF multiverktyg",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"categories": {
|
||||
"favorites": "Favoriler",
|
||||
"popularTools": "Popüler Araçlar",
|
||||
"editAnnotate": "Düzenle & Açıklama Ekle",
|
||||
"convertToPdf": "PDF'ye Dönüştür",
|
||||
@@ -10,6 +11,13 @@
|
||||
"input": "Giriş",
|
||||
"output": "Çıkış"
|
||||
},
|
||||
"favorites": {
|
||||
"add": "Favorilere ekle",
|
||||
"remove": "Favorilerden kaldır",
|
||||
"hintLabel": "İpucu",
|
||||
"hint": "Bir aracı favorilerinize eklemek için basılı tutun.",
|
||||
"hideHint": "İpucunu gizle"
|
||||
},
|
||||
"convertToPdf": "PDF'ye Dönüştür",
|
||||
"pdfMultiTool": {
|
||||
"name": "PDF Çoklu Araç",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"categories": {
|
||||
"favorites": "Вибране",
|
||||
"popularTools": "Популярні інструменти",
|
||||
"editAnnotate": "Редагування та анотації",
|
||||
"convertToPdf": "Конвертація у PDF",
|
||||
@@ -10,6 +11,13 @@
|
||||
"input": "Ввід",
|
||||
"output": "Вивід"
|
||||
},
|
||||
"favorites": {
|
||||
"add": "Додати до вибраного",
|
||||
"remove": "Видалити з вибраного",
|
||||
"hintLabel": "Порада",
|
||||
"hint": "Натисніть і утримуйте інструмент, щоб додати його до вибраного.",
|
||||
"hideHint": "Сховати пораду"
|
||||
},
|
||||
"convertToPdf": "Конвертувати в PDF",
|
||||
"pdfMultiTool": {
|
||||
"name": "PDF мультиінструмент",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"categories": {
|
||||
"favorites": "Yêu thích",
|
||||
"popularTools": "Công cụ phổ biến",
|
||||
"editAnnotate": "Chỉnh sửa & Ghi chú",
|
||||
"convertToPdf": "Chuyển đổi sang PDF",
|
||||
@@ -10,6 +11,13 @@
|
||||
"input": "Đầu vào",
|
||||
"output": "Đầu ra"
|
||||
},
|
||||
"favorites": {
|
||||
"add": "Thêm vào yêu thích",
|
||||
"remove": "Xóa khỏi yêu thích",
|
||||
"hintLabel": "Mẹo",
|
||||
"hint": "Nhấn giữ một công cụ để thêm vào mục yêu thích.",
|
||||
"hideHint": "Ẩn mẹo"
|
||||
},
|
||||
"convertToPdf": "Chuyển đổi sang PDF",
|
||||
"pdfMultiTool": {
|
||||
"name": "Công cụ đa năng PDF",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"categories": {
|
||||
"favorites": "我的最愛",
|
||||
"popularTools": "熱門工具",
|
||||
"editAnnotate": "編輯與註解",
|
||||
"convertToPdf": "轉換為 PDF",
|
||||
@@ -10,6 +11,13 @@
|
||||
"input": "輸入",
|
||||
"output": "輸出"
|
||||
},
|
||||
"favorites": {
|
||||
"add": "加入我的最愛",
|
||||
"remove": "從我的最愛移除",
|
||||
"hintLabel": "提示",
|
||||
"hint": "長按工具即可加入我的最愛。",
|
||||
"hideHint": "隱藏提示"
|
||||
},
|
||||
"convertToPdf": "轉換為 PDF",
|
||||
"pdfMultiTool": {
|
||||
"name": "PDF 多功能工具",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"categories": {
|
||||
"favorites": "收藏",
|
||||
"popularTools": "热门工具",
|
||||
"editAnnotate": "编辑与注释",
|
||||
"convertToPdf": "转换为 PDF",
|
||||
@@ -10,6 +11,13 @@
|
||||
"input": "输入",
|
||||
"output": "输出"
|
||||
},
|
||||
"favorites": {
|
||||
"add": "添加到收藏",
|
||||
"remove": "从收藏中移除",
|
||||
"hintLabel": "提示",
|
||||
"hint": "长按工具即可将其添加到收藏。",
|
||||
"hideHint": "隐藏提示"
|
||||
},
|
||||
"convertToPdf": "转换为 PDF",
|
||||
"pdfMultiTool": {
|
||||
"name": "PDF 多功能工具",
|
||||
|
||||
+4
-2
@@ -1211,7 +1211,8 @@ body.lp ::selection {
|
||||
}
|
||||
|
||||
.lp .category-group:first-child,
|
||||
.lp #search-results + .category-group {
|
||||
.lp #search-results + .category-group,
|
||||
.lp .tool-fav-hint + .category-group {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
@@ -1301,7 +1302,8 @@ body.lp ::selection {
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
.lp #tool-grid:not(.compact-mode) .tool-card:hover {
|
||||
.lp #tool-grid:not(.compact-mode) .tool-card:hover,
|
||||
.lp #tool-grid:not(.compact-mode) .tool-tile:hover .tool-card {
|
||||
background: var(--lp-bg-3);
|
||||
transform: none;
|
||||
box-shadow: none;
|
||||
|
||||
@@ -123,6 +123,196 @@ body {
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
|
||||
.tool-tile {
|
||||
position: relative;
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.tool-tile > .tool-card {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.tool-fav-btn {
|
||||
position: absolute;
|
||||
top: 0.5rem;
|
||||
right: 0.5rem;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
border-radius: 0.375rem;
|
||||
background: none;
|
||||
color: #9ca3af;
|
||||
cursor: pointer;
|
||||
opacity: 0;
|
||||
transition:
|
||||
opacity 0.2s,
|
||||
color 0.2s;
|
||||
}
|
||||
|
||||
.tool-fav-btn svg {
|
||||
width: 1.125rem;
|
||||
height: 1.125rem;
|
||||
fill: currentColor;
|
||||
}
|
||||
|
||||
.tool-fav-btn .tool-fav-on,
|
||||
.tool-fav-btn.is-favorite .tool-fav-off {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tool-fav-btn.is-favorite .tool-fav-on {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.tool-fav-btn:focus-visible,
|
||||
.tool-fav-btn.is-favorite {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
.tool-tile:hover .tool-fav-btn {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.tool-fav-btn:hover {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.tool-fav-btn.is-favorite {
|
||||
color: #e3c46b;
|
||||
}
|
||||
|
||||
.tool-fav-btn:focus-visible {
|
||||
outline: 2px solid #a5b4fc;
|
||||
outline-offset: 0;
|
||||
}
|
||||
|
||||
.tool-fav-menu,
|
||||
.tool-fav-hint {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (hover: none) {
|
||||
.tool-fav-btn:not(.is-favorite) {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.tool-tile > .tool-card {
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-drag: none;
|
||||
-webkit-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.tool-fav-menu {
|
||||
position: absolute;
|
||||
right: 0.75rem;
|
||||
bottom: 0.75rem;
|
||||
left: 0.75rem;
|
||||
z-index: 2;
|
||||
display: block;
|
||||
animation: tool-fav-menu-in 0.15s ease-out;
|
||||
}
|
||||
|
||||
.tool-fav-menu-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.625rem;
|
||||
width: 100%;
|
||||
padding: 0.75rem 1rem;
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
border-radius: 0.5rem;
|
||||
background: rgb(52, 52, 52);
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.45);
|
||||
color: #f3f3f1;
|
||||
font-size: 0.9375rem;
|
||||
font-weight: 500;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.tool-fav-menu-btn svg {
|
||||
flex-shrink: 0;
|
||||
width: 1.125rem;
|
||||
height: 1.125rem;
|
||||
fill: #e3c46b;
|
||||
}
|
||||
|
||||
.tool-fav-menu-btn .tool-fav-on,
|
||||
.tool-fav-menu-btn.is-favorite .tool-fav-off {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tool-fav-menu-btn.is-favorite .tool-fav-on {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.tool-fav-hint {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
margin-bottom: 1.5rem;
|
||||
padding-block: 0.875rem;
|
||||
padding-inline: 1.125rem 0.5rem;
|
||||
border: 1px solid var(--lp-line-2, rgba(255, 255, 255, 0.2));
|
||||
background: var(--lp-bg-2, #2a2a2a);
|
||||
color: var(--lp-text-2, #b4b4b0);
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.tool-fav-hint > span {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.tool-fav-hint-label {
|
||||
color: var(--lp-accent-3, #a5b4fc);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.tool-fav-hint-close {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
flex-shrink: 0;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
background: none;
|
||||
color: var(--lp-text-2, #b4b4b0);
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
#search-results:not(.hidden) ~ .tool-fav-hint {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.compact-mode .tool-fav-menu {
|
||||
top: calc(100% - 0.25rem);
|
||||
bottom: auto;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes tool-fav-menu-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(4px);
|
||||
}
|
||||
}
|
||||
|
||||
.compact-mode .tool-tile > .tool-card {
|
||||
padding-right: 2.75rem;
|
||||
}
|
||||
|
||||
.compact-mode .tool-fav-btn {
|
||||
top: calc(50% - 1rem);
|
||||
}
|
||||
|
||||
.compact-mode .category-tools {
|
||||
grid-template-columns: 1fr !important;
|
||||
gap: 0 !important;
|
||||
|
||||
+405
-45
@@ -30,6 +30,18 @@ import {
|
||||
removeStoredItem,
|
||||
} from './utils/safe-storage.js';
|
||||
import { sanitizeTranslationHtml } from './utils/translation-html.js';
|
||||
import type { ToolCategory } from '@/types';
|
||||
|
||||
const FAVORITE_STAR_PATHS = [
|
||||
[
|
||||
'off',
|
||||
'M239.18,97.26A16.38,16.38,0,0,0,224.92,86l-59-4.76L143.14,26.15a16.36,16.36,0,0,0-30.27,0L90.11,81.23,31.08,86a16.46,16.46,0,0,0-9.37,28.86l45,38.83L53,211.75a16.38,16.38,0,0,0,24.5,17.82L128,198.49l50.53,31.08A16.4,16.4,0,0,0,203,211.75l-13.76-58.07,45-38.83A16.43,16.43,0,0,0,239.18,97.26Zm-15.34,5.47-48.7,42a8,8,0,0,0-2.56,7.91l14.88,62.8a.37.37,0,0,1-.17.48c-.18.14-.23.11-.38,0l-54.72-33.65a8,8,0,0,0-8.38,0L69.09,215.94c-.15.09-.19.12-.38,0a.37.37,0,0,1-.17-.48l14.88-62.8a8,8,0,0,0-2.56-7.91l-48.7-42c-.12-.1-.23-.19-.13-.5s.18-.27.33-.29l63.92-5.16A8,8,0,0,0,103,91.86l24.62-59.61c.08-.17.11-.25.35-.25s.27.08.35.25L153,91.86a8,8,0,0,0,6.75,4.92l63.92,5.16c.15,0,.24,0,.33.29S224,102.63,223.84,102.73Z',
|
||||
],
|
||||
[
|
||||
'on',
|
||||
'M234.29,114.85l-45,38.83L203,211.75a16.4,16.4,0,0,1-24.5,17.82L128,198.49,77.47,229.57A16.4,16.4,0,0,1,53,211.75l13.76-58.07-45-38.83A16.46,16.46,0,0,1,31.08,86l59-4.76,22.76-55.08a16.36,16.36,0,0,1,30.27,0l22.75,55.08,59,4.76a16.46,16.46,0,0,1,9.37,28.86Z',
|
||||
],
|
||||
] as const;
|
||||
declare const __BRAND_NAME__: string;
|
||||
|
||||
const init = async () => {
|
||||
@@ -159,6 +171,7 @@ const init = async () => {
|
||||
}
|
||||
|
||||
const categoryTranslationKeys: Record<string, string> = {
|
||||
Favorites: 'tools:categories.favorites',
|
||||
'Popular Tools': 'tools:categories.popularTools',
|
||||
'Edit & Annotate': 'tools:categories.editAnnotate',
|
||||
'Convert to PDF': 'tools:categories.convertToPdf',
|
||||
@@ -313,7 +326,277 @@ const init = async () => {
|
||||
}))
|
||||
.filter((category) => category.tools.length > 0);
|
||||
|
||||
filteredCategories.forEach((category) => {
|
||||
const availableTools = new Map<string, ToolCategory['tools'][number]>();
|
||||
filteredCategories.forEach((category) =>
|
||||
category.tools.forEach((tool) => {
|
||||
if (!availableTools.has(tool.id)) availableTools.set(tool.id, tool);
|
||||
})
|
||||
);
|
||||
|
||||
let favoriteToolIds: string[] = [];
|
||||
let unavailableFavoriteIds: string[] = [];
|
||||
|
||||
function loadFavorites() {
|
||||
let stored: unknown = [];
|
||||
try {
|
||||
stored = JSON.parse(getStoredItem('favoriteTools') || '[]');
|
||||
} catch {
|
||||
removeStoredItem('favoriteTools');
|
||||
}
|
||||
const ids = Array.isArray(stored)
|
||||
? [
|
||||
...new Set(
|
||||
stored.filter((id): id is string => typeof id === 'string')
|
||||
),
|
||||
]
|
||||
: [];
|
||||
favoriteToolIds = ids.filter((id) => availableTools.has(id));
|
||||
unavailableFavoriteIds = ids.filter((id) => !availableTools.has(id));
|
||||
}
|
||||
|
||||
loadFavorites();
|
||||
|
||||
function createFavoriteButton(toolId: string): HTMLButtonElement {
|
||||
const button = document.createElement('button');
|
||||
button.type = 'button';
|
||||
button.className = 'tool-fav-btn';
|
||||
button.dataset.favId = toolId;
|
||||
button.appendChild(createStarIcon());
|
||||
updateFavoriteButton(button);
|
||||
return button;
|
||||
}
|
||||
|
||||
function createStarIcon(): SVGSVGElement {
|
||||
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
||||
svg.setAttribute('viewBox', '0 0 256 256');
|
||||
svg.setAttribute('aria-hidden', 'true');
|
||||
for (const [kind, d] of FAVORITE_STAR_PATHS) {
|
||||
const path = document.createElementNS(
|
||||
'http://www.w3.org/2000/svg',
|
||||
'path'
|
||||
);
|
||||
path.setAttribute('class', `tool-fav-${kind}`);
|
||||
path.setAttribute('d', d);
|
||||
svg.appendChild(path);
|
||||
}
|
||||
return svg;
|
||||
}
|
||||
|
||||
const favoritesHint = document.createElement('div');
|
||||
favoritesHint.className = 'tool-fav-hint col-span-full';
|
||||
const favoritesHintText = document.createElement('span');
|
||||
const favoritesHintLabel = document.createElement('strong');
|
||||
favoritesHintLabel.className = 'tool-fav-hint-label';
|
||||
favoritesHintLabel.textContent = t('tools:favorites.hintLabel');
|
||||
favoritesHintText.append(
|
||||
favoritesHintLabel,
|
||||
document.createTextNode(` ${t('tools:favorites.hint')}`)
|
||||
);
|
||||
const favoritesHintClose = document.createElement('button');
|
||||
favoritesHintClose.type = 'button';
|
||||
favoritesHintClose.className = 'tool-fav-hint-close';
|
||||
favoritesHintClose.setAttribute(
|
||||
'aria-label',
|
||||
t('tools:favorites.hideHint')
|
||||
);
|
||||
const favoritesHintIcon = document.createElement('i');
|
||||
favoritesHintIcon.className = 'ph ph-x';
|
||||
favoritesHintIcon.setAttribute('aria-hidden', 'true');
|
||||
favoritesHintClose.appendChild(favoritesHintIcon);
|
||||
favoritesHint.append(favoritesHintText, favoritesHintClose);
|
||||
favoritesHintClose.addEventListener('click', () => {
|
||||
setStoredItem('favoritesHintHidden', 'true');
|
||||
favoritesHint.remove();
|
||||
});
|
||||
|
||||
function updateFavoritesHint() {
|
||||
if (
|
||||
favoriteToolIds.length > 0 ||
|
||||
getStoredItem('favoritesHintHidden') === 'true'
|
||||
) {
|
||||
favoritesHint.remove();
|
||||
return;
|
||||
}
|
||||
if (favoritesHint.isConnected) return;
|
||||
const results = document.getElementById('search-results');
|
||||
dom.toolGrid.insertBefore(
|
||||
favoritesHint,
|
||||
results ? results.nextSibling : dom.toolGrid.firstChild
|
||||
);
|
||||
}
|
||||
|
||||
const favoriteMenu = document.createElement('div');
|
||||
favoriteMenu.className = 'tool-fav-menu';
|
||||
const favoriteMenuButton = document.createElement('button');
|
||||
favoriteMenuButton.type = 'button';
|
||||
favoriteMenuButton.className = 'tool-fav-menu-btn';
|
||||
const favoriteMenuLabel = document.createElement('span');
|
||||
favoriteMenuButton.append(createStarIcon(), favoriteMenuLabel);
|
||||
favoriteMenu.appendChild(favoriteMenuButton);
|
||||
let favoriteMenuStar: HTMLElement | null = null;
|
||||
|
||||
function openFavoriteMenu(tile: HTMLElement) {
|
||||
const star = tile.querySelector<HTMLElement>('.tool-fav-btn');
|
||||
if (!star) return;
|
||||
favoriteMenuStar = star;
|
||||
const active = favoriteToolIds.includes(star.dataset.favId || '');
|
||||
favoriteMenuButton.classList.toggle('is-favorite', active);
|
||||
favoriteMenuLabel.textContent = active
|
||||
? t('tools:favorites.remove')
|
||||
: t('tools:favorites.add');
|
||||
tile.appendChild(favoriteMenu);
|
||||
if (navigator.userActivation?.hasBeenActive) navigator.vibrate?.(10);
|
||||
}
|
||||
|
||||
function closeFavoriteMenu() {
|
||||
favoriteMenu.remove();
|
||||
favoriteMenuStar = null;
|
||||
}
|
||||
|
||||
favoriteMenuButton.addEventListener('click', (event) => {
|
||||
event.stopPropagation();
|
||||
const star = favoriteMenuStar;
|
||||
closeFavoriteMenu();
|
||||
if (star) toggleFavorite(star);
|
||||
});
|
||||
|
||||
function updateFavoriteButton(button: HTMLElement) {
|
||||
const active = favoriteToolIds.includes(button.dataset.favId || '');
|
||||
button.classList.toggle('is-favorite', active);
|
||||
button.setAttribute('aria-pressed', String(active));
|
||||
const label = active
|
||||
? t('tools:favorites.remove')
|
||||
: t('tools:favorites.add');
|
||||
button.setAttribute('aria-label', label);
|
||||
button.title = label;
|
||||
}
|
||||
|
||||
function renderFavorites() {
|
||||
const tools = favoriteToolIds.flatMap((id) => {
|
||||
const tool = availableTools.get(id);
|
||||
return tool ? [tool] : [];
|
||||
});
|
||||
if (tools.length === 0) return;
|
||||
const group = createCategoryGroup({ name: 'Favorites', tools });
|
||||
const searchBox = document.getElementById(
|
||||
'search-bar'
|
||||
) as HTMLInputElement | null;
|
||||
if (searchBox?.value.trim()) group.style.display = 'none';
|
||||
const results = document.getElementById('search-results');
|
||||
dom.toolGrid.insertBefore(
|
||||
group,
|
||||
results ? results.nextSibling : dom.toolGrid.firstChild
|
||||
);
|
||||
createIcons({ icons });
|
||||
}
|
||||
|
||||
function toggleFavorite(button: HTMLElement) {
|
||||
const toolId = button.dataset.favId;
|
||||
const tool = toolId ? availableTools.get(toolId) : undefined;
|
||||
if (!toolId || !tool) return;
|
||||
const adding = !favoriteToolIds.includes(toolId);
|
||||
favoriteToolIds = adding
|
||||
? [...favoriteToolIds, toolId]
|
||||
: favoriteToolIds.filter((id) => id !== toolId);
|
||||
setStoredItem(
|
||||
'favoriteTools',
|
||||
JSON.stringify([...favoriteToolIds, ...unavailableFavoriteIds])
|
||||
);
|
||||
|
||||
const anchorTop = button.getBoundingClientRect().top;
|
||||
const keyboardFocus = button.matches(':focus-visible');
|
||||
const group = dom.toolGrid.querySelector<HTMLElement>(
|
||||
':scope > .is-cat-favorites'
|
||||
);
|
||||
let nextFocus: HTMLElement | null = null;
|
||||
|
||||
if (adding && group) {
|
||||
group
|
||||
.querySelector('.category-tools')
|
||||
?.appendChild(createToolTile(tool));
|
||||
createIcons({ icons });
|
||||
} else if (adding) {
|
||||
renderFavorites();
|
||||
} else if (group) {
|
||||
const tile = group
|
||||
.querySelector(`.tool-fav-btn[data-fav-id="${CSS.escape(toolId)}"]`)
|
||||
?.closest('.tool-tile');
|
||||
if (tile?.contains(button)) {
|
||||
const neighbor =
|
||||
tile.nextElementSibling ?? tile.previousElementSibling;
|
||||
nextFocus =
|
||||
neighbor?.querySelector<HTMLElement>('.tool-fav-btn') ??
|
||||
group.nextElementSibling?.querySelector<HTMLElement>(
|
||||
'.category-header'
|
||||
) ??
|
||||
null;
|
||||
}
|
||||
tile?.remove();
|
||||
if (favoriteToolIds.length === 0) group.remove();
|
||||
}
|
||||
|
||||
dom.toolGrid
|
||||
.querySelectorAll<HTMLElement>('.tool-fav-btn')
|
||||
.forEach((other) => {
|
||||
if (other.dataset.favId === toolId) updateFavoriteButton(other);
|
||||
});
|
||||
updateFavoritesHint();
|
||||
|
||||
if (button.isConnected) {
|
||||
const shift = button.getBoundingClientRect().top - anchorTop;
|
||||
if (shift) window.scrollBy({ top: shift, behavior: 'instant' });
|
||||
} else if (keyboardFocus) {
|
||||
nextFocus?.focus({ preventScroll: true });
|
||||
}
|
||||
}
|
||||
|
||||
function createToolTile(tool: ToolCategory['tools'][number]) {
|
||||
const tile = document.createElement('div');
|
||||
tile.className = 'tool-tile';
|
||||
let toolCard: HTMLDivElement | HTMLAnchorElement;
|
||||
|
||||
if (tool.href) {
|
||||
toolCard = document.createElement('a');
|
||||
toolCard.href = tool.href;
|
||||
toolCard.className =
|
||||
'tool-card block bg-gray-800 rounded-xl p-4 cursor-pointer flex flex-col items-center justify-center text-center no-underline hover:shadow-lg transition duration-200';
|
||||
} else {
|
||||
toolCard = document.createElement('div');
|
||||
toolCard.className =
|
||||
'tool-card bg-gray-800 rounded-xl p-4 cursor-pointer flex flex-col items-center justify-center text-center hover:shadow-lg transition duration-200';
|
||||
toolCard.dataset.toolId = getToolId(tool);
|
||||
}
|
||||
|
||||
const icon = document.createElement('i');
|
||||
icon.className = 'w-10 h-10 mb-3 text-indigo-400';
|
||||
|
||||
if (tool.icon.startsWith('ph-')) {
|
||||
icon.className = `ph ${tool.icon} text-4xl mb-3 text-indigo-400`;
|
||||
} else {
|
||||
icon.setAttribute('data-lucide', tool.icon);
|
||||
}
|
||||
|
||||
const toolName = document.createElement('h3');
|
||||
toolName.className = 'font-semibold text-white';
|
||||
const toolKey = toolTranslationKeys[tool.name];
|
||||
toolName.textContent = toolKey ? t(`${toolKey}.name`) : tool.name;
|
||||
|
||||
toolCard.append(icon, toolName);
|
||||
|
||||
if (tool.subtitle) {
|
||||
const toolSubtitle = document.createElement('p');
|
||||
toolSubtitle.className = 'text-xs text-gray-400 mt-1 px-2';
|
||||
toolSubtitle.textContent = toolKey
|
||||
? t(`${toolKey}.subtitle`)
|
||||
: tool.subtitle;
|
||||
toolCard.appendChild(toolSubtitle);
|
||||
}
|
||||
|
||||
tile.append(toolCard, createFavoriteButton(tool.id));
|
||||
return tile;
|
||||
}
|
||||
|
||||
function createCategoryGroup(category: ToolCategory): HTMLDivElement {
|
||||
const categoryGroup = document.createElement('div');
|
||||
const categorySlug = category.name
|
||||
.toLowerCase()
|
||||
@@ -376,69 +659,40 @@ const init = async () => {
|
||||
});
|
||||
|
||||
category.tools.forEach((tool) => {
|
||||
let toolCard: HTMLDivElement | HTMLAnchorElement;
|
||||
|
||||
if (tool.href) {
|
||||
toolCard = document.createElement('a');
|
||||
toolCard.href = tool.href;
|
||||
toolCard.className =
|
||||
'tool-card block bg-gray-800 rounded-xl p-4 cursor-pointer flex flex-col items-center justify-center text-center no-underline hover:shadow-lg transition duration-200';
|
||||
} else {
|
||||
toolCard = document.createElement('div');
|
||||
toolCard.className =
|
||||
'tool-card bg-gray-800 rounded-xl p-4 cursor-pointer flex flex-col items-center justify-center text-center hover:shadow-lg transition duration-200';
|
||||
toolCard.dataset.toolId = getToolId(tool);
|
||||
}
|
||||
|
||||
const icon = document.createElement('i');
|
||||
icon.className = 'w-10 h-10 mb-3 text-indigo-400';
|
||||
|
||||
if (tool.icon.startsWith('ph-')) {
|
||||
icon.className = `ph ${tool.icon} text-4xl mb-3 text-indigo-400`;
|
||||
} else {
|
||||
icon.setAttribute('data-lucide', tool.icon);
|
||||
}
|
||||
|
||||
const toolName = document.createElement('h3');
|
||||
toolName.className = 'font-semibold text-white';
|
||||
const toolKey = toolTranslationKeys[tool.name];
|
||||
toolName.textContent = toolKey ? t(`${toolKey}.name`) : tool.name;
|
||||
|
||||
toolCard.append(icon, toolName);
|
||||
|
||||
if (tool.subtitle) {
|
||||
const toolSubtitle = document.createElement('p');
|
||||
toolSubtitle.className = 'text-xs text-gray-400 mt-1 px-2';
|
||||
toolSubtitle.textContent = toolKey
|
||||
? t(`${toolKey}.subtitle`)
|
||||
: tool.subtitle;
|
||||
toolCard.appendChild(toolSubtitle);
|
||||
}
|
||||
|
||||
toolsContainer.appendChild(toolCard);
|
||||
toolsContainer.appendChild(createToolTile(tool));
|
||||
});
|
||||
|
||||
categoryGroup.append(header, toolsContainer);
|
||||
dom.toolGrid.appendChild(categoryGroup);
|
||||
|
||||
if (!isCollapsed) {
|
||||
toolsContainer.style.maxHeight = 'none';
|
||||
toolsContainer.style.overflow = 'visible';
|
||||
}
|
||||
|
||||
return categoryGroup;
|
||||
}
|
||||
|
||||
filteredCategories.forEach((category) => {
|
||||
dom.toolGrid.appendChild(createCategoryGroup(category));
|
||||
});
|
||||
|
||||
const searchBar = document.getElementById('search-bar');
|
||||
const categoryGroups = dom.toolGrid.querySelectorAll('.category-group');
|
||||
|
||||
const searchResultsContainer = document.createElement('div');
|
||||
searchResultsContainer.id = 'search-results';
|
||||
searchResultsContainer.className =
|
||||
'hidden grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-4 md:gap-6 col-span-full';
|
||||
dom.toolGrid.insertBefore(searchResultsContainer, dom.toolGrid.firstChild);
|
||||
renderFavorites();
|
||||
updateFavoritesHint();
|
||||
|
||||
searchBar.addEventListener('input', () => {
|
||||
// @ts-expect-error TS(2339) FIXME: Property 'value' does not exist on type 'HTMLEleme... Remove this comment to see the full error message
|
||||
const searchTerm = searchBar.value.toLowerCase().trim();
|
||||
closeFavoriteMenu();
|
||||
const categoryGroups = dom.toolGrid.querySelectorAll(
|
||||
':scope > .category-group'
|
||||
);
|
||||
|
||||
if (!searchTerm) {
|
||||
searchResultsContainer.classList.add('hidden');
|
||||
@@ -487,7 +741,11 @@ const init = async () => {
|
||||
|
||||
if (isMatch && !isDuplicate) {
|
||||
seenToolIds.add(toolId);
|
||||
allTools.push(card.cloneNode(true) as HTMLElement);
|
||||
allTools.push(
|
||||
(card.closest('.tool-tile') ?? card).cloneNode(
|
||||
true
|
||||
) as HTMLElement
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -511,8 +769,110 @@ const init = async () => {
|
||||
}
|
||||
});
|
||||
|
||||
dom.toolGrid.addEventListener('click', () => {
|
||||
dom.toolGrid.addEventListener('click', (event) => {
|
||||
// All tools now use href and navigate directly - no modal handling needed
|
||||
const favoriteButton = (event.target as Element).closest<HTMLElement>(
|
||||
'.tool-fav-btn'
|
||||
);
|
||||
if (favoriteButton) toggleFavorite(favoriteButton);
|
||||
});
|
||||
|
||||
let pressTimer = 0;
|
||||
let pressStart: { x: number; y: number } | null = null;
|
||||
let suppressClick = false;
|
||||
const touchOnly = window.matchMedia('(hover: none)');
|
||||
|
||||
function cancelPress() {
|
||||
window.clearTimeout(pressTimer);
|
||||
pressStart = null;
|
||||
}
|
||||
|
||||
function pressedTile(target: EventTarget | null): HTMLElement | null {
|
||||
const element = target instanceof Element ? target : null;
|
||||
if (!element || element.closest('.tool-fav-btn, .tool-fav-menu')) {
|
||||
return null;
|
||||
}
|
||||
return element.closest<HTMLElement>('.tool-tile');
|
||||
}
|
||||
|
||||
dom.toolGrid.addEventListener('pointerdown', (event) => {
|
||||
suppressClick = false;
|
||||
if (event.pointerType !== 'touch' || !touchOnly.matches) return;
|
||||
const tile = pressedTile(event.target);
|
||||
if (!tile) return;
|
||||
pressStart = { x: event.clientX, y: event.clientY };
|
||||
pressTimer = window.setTimeout(() => {
|
||||
pressStart = null;
|
||||
suppressClick = true;
|
||||
openFavoriteMenu(tile);
|
||||
}, 500);
|
||||
});
|
||||
|
||||
dom.toolGrid.addEventListener('pointermove', (event) => {
|
||||
if (
|
||||
pressStart &&
|
||||
Math.hypot(event.clientX - pressStart.x, event.clientY - pressStart.y) >
|
||||
10
|
||||
) {
|
||||
cancelPress();
|
||||
}
|
||||
});
|
||||
|
||||
dom.toolGrid.addEventListener('pointerup', cancelPress);
|
||||
dom.toolGrid.addEventListener('pointercancel', cancelPress);
|
||||
|
||||
dom.toolGrid.addEventListener('contextmenu', (event) => {
|
||||
if (!touchOnly.matches) return;
|
||||
const tile = pressedTile(event.target);
|
||||
if (!tile) return;
|
||||
event.preventDefault();
|
||||
cancelPress();
|
||||
if (!tile.contains(favoriteMenu)) {
|
||||
suppressClick = true;
|
||||
openFavoriteMenu(tile);
|
||||
}
|
||||
});
|
||||
|
||||
dom.toolGrid.addEventListener(
|
||||
'click',
|
||||
(event) => {
|
||||
if (!suppressClick) return;
|
||||
suppressClick = false;
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
},
|
||||
true
|
||||
);
|
||||
|
||||
document.addEventListener('pointerdown', (event) => {
|
||||
if (
|
||||
favoriteMenu.isConnected &&
|
||||
!(event.target instanceof Node && favoriteMenu.contains(event.target))
|
||||
) {
|
||||
closeFavoriteMenu();
|
||||
}
|
||||
});
|
||||
window.addEventListener('scroll', closeFavoriteMenu, { passive: true });
|
||||
document.addEventListener('keydown', (event) => {
|
||||
if (event.key === 'Escape') closeFavoriteMenu();
|
||||
});
|
||||
|
||||
window.addEventListener('storage', (event) => {
|
||||
if (
|
||||
event.key !== null &&
|
||||
event.key !== 'favoriteTools' &&
|
||||
event.key !== 'favoritesHintHidden'
|
||||
) {
|
||||
return;
|
||||
}
|
||||
closeFavoriteMenu();
|
||||
loadFavorites();
|
||||
dom.toolGrid.querySelector(':scope > .is-cat-favorites')?.remove();
|
||||
renderFavorites();
|
||||
dom.toolGrid
|
||||
.querySelectorAll<HTMLElement>('.tool-fav-btn')
|
||||
.forEach(updateFavoriteButton);
|
||||
updateFavoritesHint();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -71,3 +71,4 @@ export * from './edit-pdf-redaction-type.ts';
|
||||
export * from './validate-signature-extraction-type.ts';
|
||||
export * from './pdf-content-tokens-type.ts';
|
||||
export * from './compress-pdf-type.ts';
|
||||
export * from './tool-category-type.ts';
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
import type { categories } from '../config/tools.ts';
|
||||
|
||||
export type ToolCategory = (typeof categories)[number];
|
||||
Reference in New Issue
Block a user