diff --git a/package-lock.json b/package-lock.json index e4198e049..f2d00d40d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -54,6 +54,7 @@ "@types/url-parse": "^1.4.11", "autoprefixer": "^10.4.21", "babel-loader": "^10.0.0", + "com.foxdebug.acode.rk.exec.proot": "file:src/plugins/proot", "com.foxdebug.acode.rk.exec.terminal": "file:src/plugins/terminal", "cordova-android": "^14.0.1", "cordova-clipboard": "^1.3.0", @@ -4334,6 +4335,10 @@ "dev": true, "license": "MIT" }, + "node_modules/com.foxdebug.acode.rk.exec.proot": { + "resolved": "src/plugins/proot", + "link": true + }, "node_modules/com.foxdebug.acode.rk.exec.terminal": { "resolved": "src/plugins/terminal", "link": true @@ -11202,7 +11207,7 @@ "src/plugins/proot": { "name": "com.foxdebug.acode.rk.exec.proot", "version": "1.0.0", - "extraneous": true, + "dev": true, "license": "MIT" }, "src/plugins/sdcard": { diff --git a/package.json b/package.json index d6ef79942..2ab7735fb 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,8 @@ "cordova-plugin-browser": {}, "cordova-plugin-sftp": {}, "cordova-plugin-system": {}, - "com.foxdebug.acode.rk.exec.terminal": {} + "com.foxdebug.acode.rk.exec.terminal": {}, + "com.foxdebug.acode.rk.exec.proot": {} }, "platforms": [ "android" @@ -62,6 +63,7 @@ "@types/url-parse": "^1.4.11", "autoprefixer": "^10.4.21", "babel-loader": "^10.0.0", + "com.foxdebug.acode.rk.exec.proot": "file:src/plugins/proot", "com.foxdebug.acode.rk.exec.terminal": "file:src/plugins/terminal", "cordova-android": "^14.0.1", "cordova-clipboard": "^1.3.0", diff --git a/src/lang/ar-ye.json b/src/lang/ar-ye.json index 645957fed..cb8bc1414 100644 --- a/src/lang/ar-ye.json +++ b/src/lang/ar-ye.json @@ -428,5 +428,8 @@ "contributors": "المساهمون", "quicktools:hyphen": "إدراج شرطة", "check for app updates": "Check for app updates", - "prompt update check consent message": "Acode can check for new app updates when you're online. Enable update checks?" + "prompt update check consent message": "Acode can check for new app updates when you're online. Enable update checks?", + "keywords": "Keywords", + "author": "Author", + "filtered by": "Filtered by" } diff --git a/src/lang/be-by.json b/src/lang/be-by.json index dafe38400..acb0ad114 100644 --- a/src/lang/be-by.json +++ b/src/lang/be-by.json @@ -429,5 +429,8 @@ "contributors": "Contributors", "quicktools:hyphen": "Insert hyphen symbol", "check for app updates": "Check for app updates", - "prompt update check consent message": "Acode can check for new app updates when you're online. Enable update checks?" + "prompt update check consent message": "Acode can check for new app updates when you're online. Enable update checks?", + "keywords": "Keywords", + "author": "Author", + "filtered by": "Filtered by" } diff --git a/src/lang/bn-bd.json b/src/lang/bn-bd.json index 3dfdb3f2f..fbcf89681 100644 --- a/src/lang/bn-bd.json +++ b/src/lang/bn-bd.json @@ -428,5 +428,8 @@ "contributors": "অবদানকারী", "quicktools:hyphen": "হাইফেন যুক্ত করুন", "check for app updates": "Check for app updates", - "prompt update check consent message": "Acode can check for new app updates when you're online. Enable update checks?" + "prompt update check consent message": "Acode can check for new app updates when you're online. Enable update checks?", + "keywords": "Keywords", + "author": "Author", + "filtered by": "Filtered by" } diff --git a/src/lang/cs-cz.json b/src/lang/cs-cz.json index 07e4de6ed..8ca1529c3 100644 --- a/src/lang/cs-cz.json +++ b/src/lang/cs-cz.json @@ -428,5 +428,8 @@ "contributors": "Contributors", "quicktools:hyphen": "Insert hyphen symbol", "check for app updates": "Check for app updates", - "prompt update check consent message": "Acode can check for new app updates when you're online. Enable update checks?" + "prompt update check consent message": "Acode can check for new app updates when you're online. Enable update checks?", + "keywords": "Keywords", + "author": "Author", + "filtered by": "Filtered by" } diff --git a/src/lang/de-de.json b/src/lang/de-de.json index 7559505cd..97774f2a0 100644 --- a/src/lang/de-de.json +++ b/src/lang/de-de.json @@ -428,5 +428,8 @@ "contributors": "Mitwirkende", "quicktools:hyphen": "Bindestrich-Symbol einfügen", "check for app updates": "Check for app updates", - "prompt update check consent message": "Acode can check for new app updates when you're online. Enable update checks?" + "prompt update check consent message": "Acode can check for new app updates when you're online. Enable update checks?", + "keywords": "Keywords", + "author": "Author", + "filtered by": "Filtered by" } diff --git a/src/lang/en-us.json b/src/lang/en-us.json index b2aec6550..73506f184 100644 --- a/src/lang/en-us.json +++ b/src/lang/en-us.json @@ -428,5 +428,8 @@ "contributors": "Contributors", "quicktools:hyphen": "Insert hyphen symbol", "check for app updates": "Check for app updates", - "prompt update check consent message": "Acode can check for new app updates when you're online. Enable update checks?" + "prompt update check consent message": "Acode can check for new app updates when you're online. Enable update checks?", + "keywords": "Keywords", + "author": "Author", + "filtered by": "Filtered by" } diff --git a/src/lang/es-sv.json b/src/lang/es-sv.json index f6032ceaa..7ec5373e7 100644 --- a/src/lang/es-sv.json +++ b/src/lang/es-sv.json @@ -428,5 +428,8 @@ "contributors": "Contributors", "quicktools:hyphen": "Insert hyphen symbol", "check for app updates": "Check for app updates", - "prompt update check consent message": "Acode can check for new app updates when you're online. Enable update checks?" + "prompt update check consent message": "Acode can check for new app updates when you're online. Enable update checks?", + "keywords": "Keywords", + "author": "Author", + "filtered by": "Filtered by" } diff --git a/src/lang/fr-fr.json b/src/lang/fr-fr.json index dce7d6ba3..1d8bfa695 100644 --- a/src/lang/fr-fr.json +++ b/src/lang/fr-fr.json @@ -428,5 +428,8 @@ "contributors": "Contributors", "quicktools:hyphen": "Insert hyphen symbol", "check for app updates": "Check for app updates", - "prompt update check consent message": "Acode can check for new app updates when you're online. Enable update checks?" + "prompt update check consent message": "Acode can check for new app updates when you're online. Enable update checks?", + "keywords": "Keywords", + "author": "Author", + "filtered by": "Filtered by" } diff --git a/src/lang/he-il.json b/src/lang/he-il.json index e0bb45c98..47eeced10 100644 --- a/src/lang/he-il.json +++ b/src/lang/he-il.json @@ -429,5 +429,8 @@ "contributors": "Contributors", "quicktools:hyphen": "Insert hyphen symbol", "check for app updates": "Check for app updates", - "prompt update check consent message": "Acode can check for new app updates when you're online. Enable update checks?" + "prompt update check consent message": "Acode can check for new app updates when you're online. Enable update checks?", + "keywords": "Keywords", + "author": "Author", + "filtered by": "Filtered by" } diff --git a/src/lang/hi-in.json b/src/lang/hi-in.json index c47af4d43..c82a22a4b 100644 --- a/src/lang/hi-in.json +++ b/src/lang/hi-in.json @@ -429,5 +429,8 @@ "contributors": "सहयोगी", "quicktools:hyphen": "हाइफ़न प्रतीक डालें", "check for app updates": "ऐप अपडेट की जांच करें", - "prompt update check consent message": "Acode can check for new app updates when you're online. Enable update checks?" + "prompt update check consent message": "Acode can check for new app updates when you're online. Enable update checks?", + "keywords": "Keywords", + "author": "Author", + "filtered by": "Filtered by" } diff --git a/src/lang/hu-hu.json b/src/lang/hu-hu.json index 0b18fef43..3902c01db 100644 --- a/src/lang/hu-hu.json +++ b/src/lang/hu-hu.json @@ -428,5 +428,8 @@ "contributors": "Közreműködők", "quicktools:hyphen": "Kötőjel beszúrása", "check for app updates": "Alkalmazásfrissítések ellenőrzése", - "prompt update check consent message": "Internetkapcsolat esetén az Acode ellenőrizheti az új alkalmazásfrissítéseket. Engedélyezi a frissítések ellenőrzését?" + "prompt update check consent message": "Internetkapcsolat esetén az Acode ellenőrizheti az új alkalmazásfrissítéseket. Engedélyezi a frissítések ellenőrzését?", + "keywords": "Keywords", + "author": "Author", + "filtered by": "Filtered by" } diff --git a/src/lang/id-id.json b/src/lang/id-id.json index 54e22d239..e23b654d4 100644 --- a/src/lang/id-id.json +++ b/src/lang/id-id.json @@ -429,5 +429,8 @@ "contributors": "Kontributor", "quicktools:hyphen": "Masukkan simbol tanda hubung", "check for app updates": "Check for app updates", - "prompt update check consent message": "Acode can check for new app updates when you're online. Enable update checks?" + "prompt update check consent message": "Acode can check for new app updates when you're online. Enable update checks?", + "keywords": "Keywords", + "author": "Author", + "filtered by": "Filtered by" } diff --git a/src/lang/ir-fa.json b/src/lang/ir-fa.json index 7f1a4266a..42eb53298 100644 --- a/src/lang/ir-fa.json +++ b/src/lang/ir-fa.json @@ -429,5 +429,8 @@ "contributors": "Contributors", "quicktools:hyphen": "Insert hyphen symbol", "check for app updates": "Check for app updates", - "prompt update check consent message": "Acode can check for new app updates when you're online. Enable update checks?" + "prompt update check consent message": "Acode can check for new app updates when you're online. Enable update checks?", + "keywords": "Keywords", + "author": "Author", + "filtered by": "Filtered by" } diff --git a/src/lang/it-it.json b/src/lang/it-it.json index 9cb10b9a6..ae0dc623a 100644 --- a/src/lang/it-it.json +++ b/src/lang/it-it.json @@ -428,5 +428,8 @@ "contributors": "Contributors", "quicktools:hyphen": "Insert hyphen symbol", "check for app updates": "Check for app updates", - "prompt update check consent message": "Acode can check for new app updates when you're online. Enable update checks?" + "prompt update check consent message": "Acode can check for new app updates when you're online. Enable update checks?", + "keywords": "Keywords", + "author": "Author", + "filtered by": "Filtered by" } diff --git a/src/lang/ja-jp.json b/src/lang/ja-jp.json index cccc8df05..0cd7eec85 100644 --- a/src/lang/ja-jp.json +++ b/src/lang/ja-jp.json @@ -428,5 +428,8 @@ "contributors": "Contributors", "quicktools:hyphen": "Insert hyphen symbol", "check for app updates": "Check for app updates", - "prompt update check consent message": "Acode can check for new app updates when you're online. Enable update checks?" + "prompt update check consent message": "Acode can check for new app updates when you're online. Enable update checks?", + "keywords": "Keywords", + "author": "Author", + "filtered by": "Filtered by" } diff --git a/src/lang/ko-kr.json b/src/lang/ko-kr.json index be5761d93..0a90b986f 100644 --- a/src/lang/ko-kr.json +++ b/src/lang/ko-kr.json @@ -428,5 +428,8 @@ "contributors": "Contributors", "quicktools:hyphen": "Insert hyphen symbol", "check for app updates": "Check for app updates", - "prompt update check consent message": "Acode can check for new app updates when you're online. Enable update checks?" + "prompt update check consent message": "Acode can check for new app updates when you're online. Enable update checks?", + "keywords": "Keywords", + "author": "Author", + "filtered by": "Filtered by" } diff --git a/src/lang/ml-in.json b/src/lang/ml-in.json index 79ddf4922..2194c23a3 100644 --- a/src/lang/ml-in.json +++ b/src/lang/ml-in.json @@ -428,5 +428,8 @@ "contributors": "Contributors", "quicktools:hyphen": "Insert hyphen symbol", "check for app updates": "Check for app updates", - "prompt update check consent message": "Acode can check for new app updates when you're online. Enable update checks?" + "prompt update check consent message": "Acode can check for new app updates when you're online. Enable update checks?", + "keywords": "Keywords", + "author": "Author", + "filtered by": "Filtered by" } diff --git a/src/lang/mm-unicode.json b/src/lang/mm-unicode.json index 67209d3ab..0fd284657 100644 --- a/src/lang/mm-unicode.json +++ b/src/lang/mm-unicode.json @@ -428,5 +428,8 @@ "contributors": "Contributors", "quicktools:hyphen": "Insert hyphen symbol", "check for app updates": "Check for app updates", - "prompt update check consent message": "Acode can check for new app updates when you're online. Enable update checks?" + "prompt update check consent message": "Acode can check for new app updates when you're online. Enable update checks?", + "keywords": "Keywords", + "author": "Author", + "filtered by": "Filtered by" } diff --git a/src/lang/mm-zawgyi.json b/src/lang/mm-zawgyi.json index 6794d84dc..592873ccc 100644 --- a/src/lang/mm-zawgyi.json +++ b/src/lang/mm-zawgyi.json @@ -428,5 +428,8 @@ "contributors": "Contributors", "quicktools:hyphen": "Insert hyphen symbol", "check for app updates": "Check for app updates", - "prompt update check consent message": "Acode can check for new app updates when you're online. Enable update checks?" + "prompt update check consent message": "Acode can check for new app updates when you're online. Enable update checks?", + "keywords": "Keywords", + "author": "Author", + "filtered by": "Filtered by" } diff --git a/src/lang/pl-pl.json b/src/lang/pl-pl.json index 59af10377..f28fab450 100644 --- a/src/lang/pl-pl.json +++ b/src/lang/pl-pl.json @@ -428,5 +428,8 @@ "contributors": "Contributors", "quicktools:hyphen": "Insert hyphen symbol", "check for app updates": "Check for app updates", - "prompt update check consent message": "Acode can check for new app updates when you're online. Enable update checks?" + "prompt update check consent message": "Acode can check for new app updates when you're online. Enable update checks?", + "keywords": "Keywords", + "author": "Author", + "filtered by": "Filtered by" } diff --git a/src/lang/pt-br.json b/src/lang/pt-br.json index a96d723e2..a6867794c 100644 --- a/src/lang/pt-br.json +++ b/src/lang/pt-br.json @@ -428,5 +428,8 @@ "contributors": "Contribuidores", "quicktools:hyphen": "Inserir símbolo de hífen", "check for app updates": "Check for app updates", - "prompt update check consent message": "Acode can check for new app updates when you're online. Enable update checks?" + "prompt update check consent message": "Acode can check for new app updates when you're online. Enable update checks?", + "keywords": "Keywords", + "author": "Author", + "filtered by": "Filtered by" } diff --git a/src/lang/pu-in.json b/src/lang/pu-in.json index b8fc37e74..93ad13448 100644 --- a/src/lang/pu-in.json +++ b/src/lang/pu-in.json @@ -428,5 +428,8 @@ "contributors": "Contributors", "quicktools:hyphen": "Insert hyphen symbol", "check for app updates": "Check for app updates", - "prompt update check consent message": "Acode can check for new app updates when you're online. Enable update checks?" + "prompt update check consent message": "Acode can check for new app updates when you're online. Enable update checks?", + "keywords": "Keywords", + "author": "Author", + "filtered by": "Filtered by" } diff --git a/src/lang/ru-ru.json b/src/lang/ru-ru.json index 25a98ef62..98d088c09 100644 --- a/src/lang/ru-ru.json +++ b/src/lang/ru-ru.json @@ -428,5 +428,8 @@ "contributors": "Авторы", "quicktools:hyphen": "Вставить символ дефиса", "check for app updates": "Check for app updates", - "prompt update check consent message": "Acode can check for new app updates when you're online. Enable update checks?" + "prompt update check consent message": "Acode can check for new app updates when you're online. Enable update checks?", + "keywords": "Keywords", + "author": "Author", + "filtered by": "Filtered by" } diff --git a/src/lang/tl-ph.json b/src/lang/tl-ph.json index ef3269a99..874736e9f 100644 --- a/src/lang/tl-ph.json +++ b/src/lang/tl-ph.json @@ -428,5 +428,8 @@ "contributors": "Contributors", "quicktools:hyphen": "Insert hyphen symbol", "check for app updates": "Check for app updates", - "prompt update check consent message": "Acode can check for new app updates when you're online. Enable update checks?" + "prompt update check consent message": "Acode can check for new app updates when you're online. Enable update checks?", + "keywords": "Keywords", + "author": "Author", + "filtered by": "Filtered by" } diff --git a/src/lang/tr-tr.json b/src/lang/tr-tr.json index c2fa96353..c33509167 100644 --- a/src/lang/tr-tr.json +++ b/src/lang/tr-tr.json @@ -428,5 +428,8 @@ "contributors": "Contributors", "quicktools:hyphen": "Insert hyphen symbol", "check for app updates": "Check for app updates", - "prompt update check consent message": "Acode can check for new app updates when you're online. Enable update checks?" + "prompt update check consent message": "Acode can check for new app updates when you're online. Enable update checks?", + "keywords": "Keywords", + "author": "Author", + "filtered by": "Filtered by" } diff --git a/src/lang/uk-ua.json b/src/lang/uk-ua.json index 42088a861..c2784ea9c 100644 --- a/src/lang/uk-ua.json +++ b/src/lang/uk-ua.json @@ -428,5 +428,8 @@ "contributors": "Contributors", "quicktools:hyphen": "Insert hyphen symbol", "check for app updates": "Check for app updates", - "prompt update check consent message": "Acode can check for new app updates when you're online. Enable update checks?" + "prompt update check consent message": "Acode can check for new app updates when you're online. Enable update checks?", + "keywords": "Keywords", + "author": "Author", + "filtered by": "Filtered by" } diff --git a/src/lang/uz-uz.json b/src/lang/uz-uz.json index d0b834fc8..7528eec4d 100644 --- a/src/lang/uz-uz.json +++ b/src/lang/uz-uz.json @@ -428,5 +428,8 @@ "contributors": "Contributors", "quicktools:hyphen": "Insert hyphen symbol", "check for app updates": "Check for app updates", - "prompt update check consent message": "Acode can check for new app updates when you're online. Enable update checks?" + "prompt update check consent message": "Acode can check for new app updates when you're online. Enable update checks?", + "keywords": "Keywords", + "author": "Author", + "filtered by": "Filtered by" } diff --git a/src/lang/vi-vn.json b/src/lang/vi-vn.json index 2ff79fe50..85df2bdbb 100644 --- a/src/lang/vi-vn.json +++ b/src/lang/vi-vn.json @@ -429,5 +429,8 @@ "contributors": "Contributors", "quicktools:hyphen": "Insert hyphen symbol", "check for app updates": "Check for app updates", - "prompt update check consent message": "Acode can check for new app updates when you're online. Enable update checks?" + "prompt update check consent message": "Acode can check for new app updates when you're online. Enable update checks?", + "keywords": "Keywords", + "author": "Author", + "filtered by": "Filtered by" } diff --git a/src/lang/zh-cn.json b/src/lang/zh-cn.json index 6800c9be3..b72668b1f 100644 --- a/src/lang/zh-cn.json +++ b/src/lang/zh-cn.json @@ -428,5 +428,8 @@ "contributors": "Contributors", "quicktools:hyphen": "Insert hyphen symbol", "check for app updates": "Check for app updates", - "prompt update check consent message": "Acode can check for new app updates when you're online. Enable update checks?" + "prompt update check consent message": "Acode can check for new app updates when you're online. Enable update checks?", + "keywords": "Keywords", + "author": "Author", + "filtered by": "Filtered by" } diff --git a/src/lang/zh-hant.json b/src/lang/zh-hant.json index ad2c650d8..54202d18b 100644 --- a/src/lang/zh-hant.json +++ b/src/lang/zh-hant.json @@ -428,5 +428,8 @@ "contributors": "Contributors", "quicktools:hyphen": "Insert hyphen symbol", "check for app updates": "Check for app updates", - "prompt update check consent message": "Acode can check for new app updates when you're online. Enable update checks?" + "prompt update check consent message": "Acode can check for new app updates when you're online. Enable update checks?", + "keywords": "Keywords", + "author": "Author", + "filtered by": "Filtered by" } diff --git a/src/lang/zh-tw.json b/src/lang/zh-tw.json index 7f9a24980..becdaef5b 100644 --- a/src/lang/zh-tw.json +++ b/src/lang/zh-tw.json @@ -428,5 +428,8 @@ "contributors": "Contributors", "quicktools:hyphen": "Insert hyphen symbol", "check for app updates": "Check for app updates", - "prompt update check consent message": "Acode can check for new app updates when you're online. Enable update checks?" + "prompt update check consent message": "Acode can check for new app updates when you're online. Enable update checks?", + "keywords": "Keywords", + "author": "Author", + "filtered by": "Filtered by" } diff --git a/src/pages/plugins/plugins.js b/src/pages/plugins/plugins.js index 735e225f6..a0f0bcbe9 100644 --- a/src/pages/plugins/plugins.js +++ b/src/pages/plugins/plugins.js @@ -66,48 +66,108 @@ export default function PluginsInclude(updates) { }, }); + const verifiedLabel = strings["verified publisher"]; + const authorLabel = strings.author || strings.name; + const keywordsLabel = strings.keywords; + + const filterOptions = { + "orderBy:top_rated": { type: "orderBy", value: "top_rated", baseLabel: strings.top_rated }, + "orderBy:newest": { type: "orderBy", value: "newest", baseLabel: strings.newly_added }, + "orderBy:downloads": { type: "orderBy", value: "downloads", baseLabel: strings.most_downloaded }, + "attribute:verified": { type: "verified", value: true, baseLabel: verifiedLabel }, + "attribute:author": { type: "author", baseLabel: authorLabel }, + "attribute:keywords": { type: "keywords", baseLabel: keywordsLabel }, + }; + + async function applyFilter(filterState) { + if (!filterState) return; + + const normalizedFilter = { + ...filterState, + displayLabel: filterState.displayLabel || filterState.baseLabel, + nextPage: 1, + buffer: [], + hasMoreSource: true, + }; + + currentFilter = normalizedFilter; + currentPage = 1; + hasMore = true; + isLoading = false; + plugins.all = []; + + if (currSection !== "all") { + render("all"); + } else { + $list.all.replaceChildren(); + } + + const filterMessage = ( +
+ {strings["filtered by"]} {normalizedFilter.displayLabel} + +
+ ); + + $list.all.append(filterMessage); + $list.all.setAttribute("empty-msg", strings["loading..."]); + await getFilteredPlugins(currentFilter, true); + } + + function clearFilter() { + currentFilter = null; + currentPage = 1; + hasMore = true; + isLoading = false; + plugins.all = []; + $list.all.replaceChildren(); + $list.all.setAttribute("empty-msg", strings["loading..."]); + getAllPlugins(); + } + Contextmenu({ toggler: $filter, top: "8px", right: "16px", items: [ - [strings.top_rated, "top_rated"], - [strings.newly_added, "newest"], - [strings.most_downloaded, "downloads"], + [strings.top_rated, "orderBy:top_rated"], + [strings.newly_added, "orderBy:newest"], + [strings.most_downloaded, "orderBy:downloads"], + [verifiedLabel, "attribute:verified"], + [authorLabel, "attribute:author"], + [keywordsLabel, "attribute:keywords"], ], - onselect(item) { - const filterNames = { - top_rated: strings.top_rated, - newest: strings.newly_added, - downloads: strings.most_downloaded, + async onselect(action) { + const option = filterOptions[action]; + if (!option) return; + + const filterState = { + type: option.type, + value: option.value, + baseLabel: option.baseLabel, + displayLabel: option.baseLabel, }; - const filterName = filterNames[item]; - currentFilter = item; - currentPage = 1; - hasMore = true; - isLoading = false; - plugins.all = []; // Reset the all plugins array - render("all"); - $list.all.replaceChildren(); - $list.all.append( -
- Filtered by {filterName} - { - currentFilter = null; - currentPage = 1; - hasMore = true; - isLoading = false; - plugins.all = []; // Reset the all plugins array - $list.all.replaceChildren(); - getAllPlugins(); - }} - > -
, - ); - getFilteredPlugins(item); + + if (option.type === "author") { + const authorName = (await prompt("Enter author name", "", "text"))?.trim(); + if (!authorName) return; + filterState.value = authorName.toLowerCase(); + filterState.originalValue = authorName; + filterState.displayLabel = `${option.baseLabel}: ${authorName}`; + } else if (option.type === "keywords") { + const rawKeywords = (await prompt("Enter keywords", "", "text"))?.trim(); + if (!rawKeywords) return; + const keywordList = rawKeywords + .split(",") + .map((item) => item.trim()) + .filter(Boolean); + if (!keywordList.length) return; + filterState.value = keywordList.map((item) => item.toLowerCase()); + filterState.originalValue = keywordList.join(", "); + filterState.displayLabel = `${option.baseLabel}: ${filterState.originalValue}`; + } + + await applyFilter(filterState); }, }); @@ -250,7 +310,9 @@ export default function PluginsInclude(updates) { isLoading = false; plugins.all = []; // Reset the all plugins array $list.all.replaceChildren(); - getAllPlugins(); + if (!currentFilter) { + getAllPlugins(); + } } $page.get(".options .active").classList.remove("active"); $page.get(`#${section}_plugins`).classList.add("active"); @@ -258,6 +320,9 @@ export default function PluginsInclude(updates) { function renderAll() { render("all"); + if (currentFilter) { + applyFilter(currentFilter); + } } function renderInstalled() { @@ -285,32 +350,25 @@ export default function PluginsInclude(updates) { } } - async function getFilteredPlugins(filterName) { + async function getFilteredPlugins(filterState, isInitial = false) { + if (!filterState) return; if (isLoading || !hasMore) return; - + try { isLoading = true; $list.all.setAttribute("empty-msg", strings["loading..."]); - - let response; - if (filterName === "top_rated") { - response = await fetch(`${constants.API_BASE}/plugins?explore=random&page=${currentPage}&limit=${LIMIT}`); - } else { - response = await fetch( - `${constants.API_BASE}/plugin?orderBy=${filterName}&page=${currentPage}&limit=${LIMIT}`, - ); - } - const fetchedPlugins = await response.json(); - - if (fetchedPlugins.length < LIMIT) { - hasMore = false; + + const { items, hasMore: hasMoreResults } = await retrieveFilteredPlugins(filterState); + + if (currentFilter !== filterState) { + return; } - + const installed = await fsOperation(PLUGIN_DIR).lsDir(); const disabledMap = settings.value.pluginsDisabled || {}; - + installed.forEach(({ url }) => { - const plugin = fetchedPlugins.find(({ id }) => id === Url.basename(url)); + const plugin = items.find(({ id }) => id === Url.basename(url)); if (plugin) { plugin.installed = true; plugin.enabled = disabledMap[plugin.id] !== true; @@ -318,28 +376,180 @@ export default function PluginsInclude(updates) { plugin.localPlugin = getLocalRes(plugin.id, "plugin.json"); } }); - - // Add plugins to the all plugins array - plugins.all.push(...fetchedPlugins); - + + if (isInitial) { + $list.all.querySelectorAll(".filter-empty").forEach((el) => el.remove()); + } + + plugins.all.push(...items); + const fragment = document.createDocumentFragment(); - fetchedPlugins.forEach((plugin) => { + items.forEach((plugin) => { fragment.append(); }); - $list.all.append(fragment); - - currentPage++; - $list.all.setAttribute("empty-msg", strings["no plugins found"]); + + if (fragment.childNodes.length) { + $list.all.append(fragment); + } else if (isInitial) { + $list.all.append( +
{strings["no plugins found"] || "No plugins found"}
, + ); + } + + hasMore = hasMoreResults; + if (!hasMore) { + $list.all.setAttribute("empty-msg", strings["no plugins found"]); + } } catch (error) { $list.all.setAttribute("empty-msg", strings["error"]); - window.log("error", "Failed to filter plugins:"); - window.log("error", error); + console.error("Failed to filter plugins:", error); + hasMore = false; } finally { isLoading = false; } } + async function retrieveFilteredPlugins(filterState) { + if (!filterState) return { items: [], hasMore: false }; + + if (filterState.type === "orderBy") { + const page = filterState.nextPage || 1; + try { + let response; + if (filterState.value === "top_rated") { + response = await fetch( + `${constants.API_BASE}/plugins?explore=random&page=${page}&limit=${LIMIT}`, + ); + } else { + response = await fetch( + `${constants.API_BASE}/plugin?orderBy=${filterState.value}&page=${page}&limit=${LIMIT}`, + ); + } + const items = await response.json(); + if (!Array.isArray(items)) { + return { items: [], hasMore: false }; + } + filterState.nextPage = page + 1; + const hasMoreResults = items.length === LIMIT; + return { items, hasMore: hasMoreResults }; + } catch (error) { + console.error("Failed to fetch ordered plugins:", error); + return { items: [], hasMore: false }; + } + } + + if (!Array.isArray(filterState.buffer)) { + filterState.buffer = []; + } + if (filterState.hasMoreSource === undefined) { + filterState.hasMoreSource = true; + } + if (!filterState.nextPage) { + filterState.nextPage = 1; + } + + const items = []; + + while (items.length < LIMIT) { + if (filterState.buffer.length) { + items.push(filterState.buffer.shift()); + continue; + } + + if (filterState.hasMoreSource === false) break; + + try { + const page = filterState.nextPage; + const response = await fetch( + `${constants.API_BASE}/plugins?page=${page}&limit=${LIMIT}`, + ); + const data = await response.json(); + filterState.nextPage = page + 1; + + if (!Array.isArray(data) || !data.length) { + filterState.hasMoreSource = false; + break; + } + + if (data.length < LIMIT) { + filterState.hasMoreSource = false; + } + + const matched = data.filter((plugin) => matchesFilter(plugin, filterState)); + filterState.buffer.push(...matched); + } catch (error) { + console.error("Failed to fetch filtered plugins:", error); + filterState.hasMoreSource = false; + break; + } + } + + while (items.length < LIMIT && filterState.buffer.length) { + items.push(filterState.buffer.shift()); + } + + const hasMoreResults = + (filterState.hasMoreSource !== false && filterState.nextPage) || + filterState.buffer.length > 0; + + return { items, hasMore: Boolean(hasMoreResults) }; + } + + function matchesFilter(plugin, filterState) { + if (!plugin) return false; + + switch (filterState.type) { + case "verified": + return Boolean(plugin.author_verified); + case "author": { + const authorName = normalizePluginAuthor(plugin); + if (!authorName) return false; + return authorName.toLowerCase().includes(filterState.value); + } + case "keywords": { + const pluginKeywords = normalizePluginKeywords(plugin) + .map((keyword) => keyword.toLowerCase()) + .filter(Boolean); + if (!pluginKeywords.length) return false; + return filterState.value.some((keyword) => + pluginKeywords.some((pluginKeyword) => pluginKeyword.includes(keyword)), + ); + } + default: + return true; + } + } + + function normalizePluginAuthor(plugin) { + const { author } = plugin || {}; + if (!author) return ""; + if (typeof author === "string") return author; + if (typeof author === "object") { + return author.name || author.username || author.github || ""; + } + return ""; + } + + function normalizePluginKeywords(plugin) { + const { keywords } = plugin || {}; + if (!keywords) return []; + if (Array.isArray(keywords)) return keywords; + if (typeof keywords === "string") { + try { + const parsed = JSON.parse(keywords); + if (Array.isArray(parsed)) return parsed; + } catch (error) { + return keywords + .split(",") + .map((item) => item.trim()) + .filter(Boolean); + } + } + return []; + } + async function getAllPlugins() { + if (currentFilter) return; if (isLoading || !hasMore) return; try { @@ -356,7 +566,7 @@ export default function PluginsInclude(updates) { const installed = await fsOperation(PLUGIN_DIR).lsDir(); const disabledMap = settings.value.pluginsDisabled || {}; - + installed.forEach(({ url }) => { const plugin = newPlugins.find(({ id }) => id === Url.basename(url)); if (plugin) { @@ -369,7 +579,7 @@ export default function PluginsInclude(updates) { // Add plugins to the all plugins array plugins.all.push(...newPlugins); - + const fragment = document.createDocumentFragment(); newPlugins.forEach((plugin) => { fragment.append(); @@ -413,19 +623,19 @@ export default function PluginsInclude(updates) { $list.owned.setAttribute("empty-msg", strings["loading..."]); const purchases = await helpers.promisify(iap.getPurchases); const disabledMap = settings.value.pluginsDisabled || {}; - + purchases.forEach(async ({ productIds }) => { const [sku] = productIds; const url = Url.join(constants.API_BASE, "plugin/owned", sku); const plugin = await fsOperation(url).readFile("json"); const isInstalled = plugins.installed.find(({ id }) => id === plugin.id); plugin.installed = !!isInstalled; - + if (plugin.installed) { plugin.enabled = disabledMap[plugin.id] !== true; plugin.onToggleEnabled = onToggleEnabled; } - + plugins.owned.push(plugin); $list.owned.append(); }); @@ -525,12 +735,12 @@ export default function PluginsInclude(updates) { if (installedPlugin) { installedPlugin.enabled = !enabled; } - + const allPlugin = plugins.all.find(p => p.id === id); if (allPlugin) { allPlugin.enabled = !enabled; } - + const ownedPlugin = plugins.owned.find(p => p.id === id); if (ownedPlugin) { ownedPlugin.enabled = !enabled; @@ -542,13 +752,13 @@ export default function PluginsInclude(updates) { const $newItem = ; $installedItem.replaceWith($newItem); } - + const $allItem = $list.all.get(`[data-id="${id}"]`); if ($allItem && allPlugin) { const $newItem = ; $allItem.replaceWith($newItem); } - + const $ownedItem = $list.owned.get(`[data-id="${id}"]`); if ($ownedItem && ownedPlugin) { const $newItem = ; diff --git a/src/sidebarApps/extensions/index.js b/src/sidebarApps/extensions/index.js index 9ba78b8cd..3daaf7b06 100644 --- a/src/sidebarApps/extensions/index.js +++ b/src/sidebarApps/extensions/index.js @@ -31,7 +31,6 @@ let currentPage = 1; let hasMore = true; let isLoading = false; let currentFilter = null; -let filterCurrentPage = 1; let filterHasMore = true; let isFilterLoading = false; @@ -163,28 +162,35 @@ async function loadMorePlugins() { } } -async function loadFilteredPlugins(filterName, isInitial = false) { - if (isFilterLoading || !filterHasMore) return; +async function loadFilteredPlugins(filterState, isInitial = false) { + if (isFilterLoading || !filterHasMore || !filterState) return; try { isFilterLoading = true; - const plugins = await getFilteredPlugins(filterName, filterCurrentPage); + const { items, hasMore } = await getFilteredPlugins(filterState); - if (plugins.length < LIMIT) { - filterHasMore = false; + if (currentFilter !== filterState) { + return; } installedPlugins = await listInstalledPlugins(); - const pluginElements = plugins.map(ListItem); - - if (isInitial) { - $searchResult.append(...pluginElements); - } else { + const pluginElements = items.map(ListItem); + if (pluginElements.length) { $searchResult.append(...pluginElements); + } else if (isInitial) { + $searchResult.append( + + {strings["no plugins found"] || strings.empty || "No plugins found"} + , + ); + } + + filterHasMore = hasMore; + if (!filterHasMore) { + $searchResult.onscroll = null; } - filterCurrentPage++; updateHeight($searchResult); } catch (error) { window.log("error", "Error loading filtered plugins:"); @@ -199,7 +205,6 @@ async function searchPlugin() { searchTimeout = setTimeout(async () => { // Clear filter when searching currentFilter = null; - filterCurrentPage = 1; filterHasMore = true; isFilterLoading = false; $searchResult.onscroll = null; @@ -235,28 +240,88 @@ async function searchPlugin() { } async function filterPlugins() { - const filterOptions = { - [strings.top_rated]: "top_rated", - [strings.newly_added]: "newest", - [strings.most_downloaded]: "downloads", + const verifiedLabel = strings["verified publisher"]; + const authorLabel = strings.author || strings.name; + const keywordsLabel = strings.keywords; + + const filterItems = [ + { value: "orderBy:top_rated", text: strings.top_rated }, + { value: "orderBy:newest", text: strings.newly_added }, + { value: "orderBy:downloads", text: strings.most_downloaded }, + { value: "attribute:verified", text: verifiedLabel }, + { value: "attribute:author", text: authorLabel }, + { value: "attribute:keywords", text: keywordsLabel }, + ]; + + const filterConfig = { + "orderBy:top_rated": { + type: "orderBy", + value: "top_rated", + baseLabel: strings.top_rated, + }, + "orderBy:newest": { + type: "orderBy", + value: "newest", + baseLabel: strings.newly_added, + }, + "orderBy:downloads": { + type: "orderBy", + value: "downloads", + baseLabel: strings.most_downloaded, + }, + "attribute:verified": { + type: "verified", + baseLabel: verifiedLabel, + value: true, + }, + "attribute:author": { type: "author", baseLabel: authorLabel }, + "attribute:keywords": { type: "keywords", baseLabel: keywordsLabel }, }; - const filterName = await select("Filter", Object.keys(filterOptions)); - if (!filterName) return; + const selection = await select("Filter", filterItems); + if (!selection) return; - $searchResult.content = ""; - const filterParam = filterOptions[filterName]; - currentFilter = filterParam; - filterCurrentPage = 1; + const option = filterConfig[selection]; + if (!option) return; + + const filterState = { + ...option, + nextPage: 1, + buffer: [], + hasMoreSource: true, + displayLabel: option.baseLabel, + }; + + if (option.type === "author") { + const authorName = (await prompt("Enter author name", "", "text"))?.trim(); + if (!authorName) return; + filterState.value = authorName.toLowerCase(); + filterState.originalValue = authorName; + filterState.displayLabel = `${option.baseLabel}: ${authorName}`; + } else if (option.type === "keywords") { + const rawKeywords = (await prompt("Enter keywords", "", "text"))?.trim(); + if (!rawKeywords) return; + const keywordList = rawKeywords + .split(",") + .map((item) => item.trim()) + .filter(Boolean); + if (!keywordList.length) return; + filterState.value = keywordList.map((item) => item.toLowerCase()); + filterState.originalValue = keywordList.join(", "); + filterState.displayLabel = `${option.baseLabel}: ${filterState.originalValue}`; + } + + currentFilter = filterState; filterHasMore = true; isFilterLoading = false; + $searchResult.content = ""; try { $searchResult.classList.add("loading"); const filterMessage = (
- Filtered by {filterName} + {strings["filtered by"]} {filterState.displayLabel} + doesPluginMatchFilter(plugin, filterState), ); + filterState.buffer.push(...matched); + } catch (error) { + window.log("error", "Failed to fetch filtered plugins:"); + window.log("error", error); + filterState.hasMoreSource = false; + break; + } + } + + while (items.length < LIMIT && filterState.buffer.length) { + items.push(filterState.buffer.shift()); + } + + const hasMore = + (filterState.hasMoreSource !== false && filterState.nextPage) || + filterState.buffer.length > 0; + + return { items, hasMore: Boolean(hasMore) }; +} + +function doesPluginMatchFilter(plugin, filterState) { + if (!plugin) return false; + + switch (filterState.type) { + case "verified": + return Boolean(plugin.author_verified); + case "author": { + const authorName = getPluginAuthorName(plugin); + if (!authorName) return false; + return authorName.toLowerCase().includes(filterState.value); + } + case "keywords": { + const pluginKeywords = getPluginKeywords(plugin) + .map((keyword) => keyword.toLowerCase()) + .filter(Boolean); + if (!pluginKeywords.length) return false; + return filterState.value.some((keyword) => + pluginKeywords.some((pluginKeyword) => pluginKeyword.includes(keyword)), + ); + } + default: + return true; + } +} + +function getPluginAuthorName(plugin) { + const { author } = plugin || {}; + if (!author) return ""; + if (typeof author === "string") return author; + if (typeof author === "object") { + return author.name || author.username || author.github || ""; + } + return ""; +} + +function getPluginKeywords(plugin) { + const { keywords } = plugin || {}; + if (!keywords) return []; + if (Array.isArray(keywords)) return keywords; + if (typeof keywords === "string") { + try { + const parsed = JSON.parse(keywords); + if (Array.isArray(parsed)) return parsed; + } catch (error) { + return keywords + .split(",") + .map((item) => item.trim()) + .filter(Boolean); } - return await response.json(); - } catch (error) { - window.log("error", error); - return []; } + return []; } function startLoading($list) { @@ -596,7 +783,6 @@ function ListItem({ icon, name, id, version, downloads, installed, source }) { $searchResult.content = ""; // Reset filter state when clearing search results currentFilter = null; - filterCurrentPage = 1; filterHasMore = true; isFilterLoading = false; $searchResult.onscroll = null; @@ -690,7 +876,6 @@ async function uninstall(id) { $searchResult.content = ""; // Reset filter state when clearing search results currentFilter = null; - filterCurrentPage = 1; filterHasMore = true; isFilterLoading = false; $searchResult.onscroll = null;