Update plugin sanbox (#1014)

* add htmlparser2

* fix typo

* flatten plugins
This commit is contained in:
nyagami
2024-03-26 17:46:38 +07:00
committed by GitHub
parent e171dfebc1
commit 7ea8ebacd1
8 changed files with 140 additions and 116 deletions
+61 -5
View File
@@ -31,6 +31,7 @@
"expo-speech": "~11.3.0",
"expo-sqlite": "~11.3.3",
"expo-web-browser": "~12.3.2",
"htmlparser2": "^9.1.0",
"i18n-js": "^3.8.0",
"lodash-es": "^4.17.21",
"protobufjs": "^7.2.6",
@@ -6756,6 +6757,25 @@
"htmlparser2": "^8.0.0"
}
},
"node_modules/@types/sanitize-html/node_modules/htmlparser2": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz",
"integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==",
"dev": true,
"funding": [
"https://github.com/fb55/htmlparser2?sponsor=1",
{
"type": "github",
"url": "https://github.com/sponsors/fb55"
}
],
"dependencies": {
"domelementtype": "^2.3.0",
"domhandler": "^5.0.3",
"domutils": "^3.0.1",
"entities": "^4.4.0"
}
},
"node_modules/@types/scheduler": {
"version": "0.16.8",
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz",
@@ -8273,6 +8293,24 @@
"url": "https://github.com/sponsors/fb55"
}
},
"node_modules/cheerio/node_modules/htmlparser2": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz",
"integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==",
"funding": [
"https://github.com/fb55/htmlparser2?sponsor=1",
{
"type": "github",
"url": "https://github.com/sponsors/fb55"
}
],
"dependencies": {
"domelementtype": "^2.3.0",
"domhandler": "^5.0.3",
"domutils": "^3.0.1",
"entities": "^4.4.0"
}
},
"node_modules/chownr": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz",
@@ -11015,9 +11053,9 @@
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
},
"node_modules/htmlparser2": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz",
"integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==",
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-9.1.0.tgz",
"integrity": "sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==",
"funding": [
"https://github.com/fb55/htmlparser2?sponsor=1",
{
@@ -11028,8 +11066,8 @@
"dependencies": {
"domelementtype": "^2.3.0",
"domhandler": "^5.0.3",
"domutils": "^3.0.1",
"entities": "^4.4.0"
"domutils": "^3.1.0",
"entities": "^4.5.0"
}
},
"node_modules/http-errors": {
@@ -16780,6 +16818,24 @@
"node": ">=0.10.0"
}
},
"node_modules/sanitize-html/node_modules/htmlparser2": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz",
"integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==",
"funding": [
"https://github.com/fb55/htmlparser2?sponsor=1",
{
"type": "github",
"url": "https://github.com/sponsors/fb55"
}
],
"dependencies": {
"domelementtype": "^2.3.0",
"domhandler": "^5.0.3",
"domutils": "^3.0.1",
"entities": "^4.4.0"
}
},
"node_modules/sanitize-html/node_modules/is-plain-object": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz",
+1
View File
@@ -37,6 +37,7 @@
"expo-speech": "~11.3.0",
"expo-sqlite": "~11.3.3",
"expo-web-browser": "~12.3.2",
"htmlparser2": "^9.1.0",
"i18n-js": "^3.8.0",
"lodash-es": "^4.17.21",
"protobufjs": "^7.2.6",
+37 -47
View File
@@ -1,5 +1,5 @@
import { locale } from 'expo-localization';
import { Language, languagesMapping } from '@utils/constants/languages';
import { languagesMapping } from '@utils/constants/languages';
import { orderBy } from 'lodash-es';
import { useMMKVObject } from 'react-native-mmkv';
import { PluginItem } from '@plugins/types';
@@ -23,17 +23,13 @@ export const FILTERED_INSTALLED_PLUGINS = 'FILTERED_INSTALLED_PLUGINS';
const defaultLang = languagesMapping[locale.split('-')[0]] || 'English';
export type PluginsMap = Record<Language, PluginItem[] | undefined>;
export default function usePlugins() {
const [lastUsedPlugin, setLastUsedPlugin] =
useMMKVObject<PluginItem>(LAST_USED_PLUGIN);
const [languagesFilter = [defaultLang], setLanguagesFilter] =
useMMKVObject<Language[]>(LANGUAGES_FILTER);
const [
filteredAvailablePlugins = {} as PluginsMap,
setFilteredAvailablePlugins,
] = useMMKVObject<PluginsMap>(FILTERED_AVAILABLE_PLUGINS);
useMMKVObject<string[]>(LANGUAGES_FILTER);
const [filteredAvailablePlugins = [], setFilteredAvailablePlugins] =
useMMKVObject<PluginItem[]>(FILTERED_AVAILABLE_PLUGINS);
const [filteredInstalledPlugins = [], setFilteredInstalledPlugins] =
useMMKVObject<PluginItem[]>(FILTERED_INSTALLED_PLUGINS);
/**
@@ -41,54 +37,51 @@ export default function usePlugins() {
* We cant use the languagesFilter directly because it is updated only after component's lifecycle end.
* And toggleLanguagFilter triggers filterPlugins before lifecycle end.
*/
const filterPlugins = useCallback((filter: Language[]) => {
const filterPlugins = useCallback((filter: string[]) => {
const installedPlugins =
getMMKVObject<PluginItem[]>(INSTALLED_PLUGINS) || [];
const availablePlugins =
getMMKVObject<PluginsMap>(AVAILABLE_PLUGINS) || ({} as PluginsMap);
getMMKVObject<PluginItem[]>(AVAILABLE_PLUGINS) || [];
setFilteredInstalledPlugins(
installedPlugins.filter(plg => filter.includes(plg.lang)),
);
setFilteredAvailablePlugins(
filter.reduce((pre, cur) => {
pre[cur] = orderBy(availablePlugins[cur], 'name');
return pre;
}, {} as PluginsMap),
orderBy(
availablePlugins.filter(plg => filter.includes(plg.lang)),
'name',
),
);
}, []);
const refreshPlugins = useCallback(() => {
const installedPlugins =
getMMKVObject<PluginItem[]>(INSTALLED_PLUGINS) || [];
return fetchPlugins().then(fetchedPluginsMap => {
for (const key in fetchedPluginsMap) {
const lang = key as Language;
fetchedPluginsMap[lang] = fetchedPluginsMap[lang].filter(plg => {
const finded = installedPlugins.find(v => v.id === plg.id);
if (finded) {
if (newer(plg.version, finded.version)) {
finded.hasUpdate = true;
finded.iconUrl = plg.iconUrl;
finded.url = plg.url;
if (finded.id === lastUsedPlugin?.id) {
setLastUsedPlugin(finded);
}
return fetchPlugins().then(fetchedPlugins => {
fetchedPlugins.filter(plg => {
const finded = installedPlugins.find(v => v.id === plg.id);
if (finded) {
if (newer(plg.version, finded.version)) {
finded.hasUpdate = true;
finded.iconUrl = plg.iconUrl;
finded.url = plg.url;
if (finded.id === lastUsedPlugin?.id) {
setLastUsedPlugin(finded);
}
return false;
}
return true;
});
setMMKVObject(INSTALLED_PLUGINS, installedPlugins);
setMMKVObject(AVAILABLE_PLUGINS, fetchedPluginsMap);
filterPlugins(languagesFilter);
}
return false;
}
return true;
});
setMMKVObject(INSTALLED_PLUGINS, installedPlugins);
setMMKVObject(AVAILABLE_PLUGINS, fetchedPlugins);
filterPlugins(languagesFilter);
});
}, [languagesFilter]);
const toggleLanguageFilter = (lang: Language) => {
const toggleLanguageFilter = (lang: string) => {
const newFilter = languagesFilter.includes(lang)
? languagesFilter.filter(l => l !== lang)
: [...languagesFilter, lang];
: [lang, ...languagesFilter];
setLanguagesFilter(newFilter);
filterPlugins(newFilter);
};
@@ -106,10 +99,7 @@ export default function usePlugins() {
const installedPlugins =
getMMKVObject<PluginItem[]>(INSTALLED_PLUGINS) || [];
const availablePlugins =
getMMKVObject<PluginsMap>(AVAILABLE_PLUGINS) || ({} as PluginsMap);
availablePlugins[plugin.lang] = availablePlugins[plugin.lang]?.filter(
plg => plg.id !== plugin.id,
);
getMMKVObject<PluginItem[]>(AVAILABLE_PLUGINS) || [];
const actualPlugin: PluginItem = {
...plugin,
@@ -119,7 +109,10 @@ export default function usePlugins() {
if (!installedPlugins.some(plg => plg.id === plugin.id)) {
setMMKVObject(INSTALLED_PLUGINS, [...installedPlugins, actualPlugin]);
}
setMMKVObject(AVAILABLE_PLUGINS, availablePlugins);
setMMKVObject(
AVAILABLE_PLUGINS,
availablePlugins.filter(plg => plg.id !== plugin.id),
);
filterPlugins(languagesFilter);
} else {
throw new Error(
@@ -136,14 +129,11 @@ export default function usePlugins() {
const installedPlugins =
getMMKVObject<PluginItem[]>(INSTALLED_PLUGINS) || [];
const availablePlugins =
getMMKVObject<PluginsMap>(AVAILABLE_PLUGINS) || ({} as PluginsMap);
getMMKVObject<PluginItem[]>(AVAILABLE_PLUGINS) || [];
// safe
if (!availablePlugins[plugin.lang]?.some(_plg => _plg.id === plugin.id)) {
availablePlugins[plugin.lang] = [
...(availablePlugins[plugin.lang] || []),
plugin,
];
if (!availablePlugins.some(_plg => _plg.id === plugin.id)) {
availablePlugins.push(plugin);
setMMKVObject(AVAILABLE_PLUGINS, availablePlugins);
}
setMMKVObject(
+5 -4
View File
@@ -1,7 +1,6 @@
import RNFS from 'react-native-fs';
import { PluginDownloadFolder } from '@utils/constants/download';
import { newer } from '@utils/compareVersion';
import { Language } from '@utils/constants/languages';
// packages for plugins
import { load } from 'cheerio';
@@ -13,11 +12,13 @@ import { isUrlAbsolute } from './helpers/isAbsoluteUrl';
import { fetchApi, fetchFile, fetchProto, fetchText } from './helpers/fetch';
import { defaultCover } from './helpers/constants';
import { encode, decode } from 'urlencode';
import { Parser } from 'htmlparser2';
import TextFile from '@native/TextFile';
const pluginsFilePath = PluginDownloadFolder + '/plugins.json';
const packages: Record<string, any> = {
'htmlparser2': { Parser },
'cheerio': { load },
'dayjs': dayjs,
'qs': qs,
@@ -125,13 +126,13 @@ const updatePlugin = async (plugin: PluginItem) => {
return installPlugin(plugin.url);
};
const fetchPlugins = (): Promise<Record<Language, Array<PluginItem>>> => {
const fetchPlugins = (): Promise<PluginItem[]> => {
// plugins host
const githubUsername = 'LNReader';
const githubRepository = 'lnreader-sources';
const pluginsTag = 'v2.1.0';
return fetch(
`https://raw.githubusercontent.com/${githubUsername}/${githubRepository}/dist/.dist/plugins.min.json`,
`https://raw.githubusercontent.com/${githubUsername}/${githubRepository}/plugins/${pluginsTag}/.dist/plugins.min.json`,
).then(res => res.json());
};
+1 -2
View File
@@ -1,5 +1,4 @@
import { FilterToValues, Filters } from './filterTypes';
import { Language } from '@utils/constants/languages';
export interface NovelItem {
name: string;
@@ -50,7 +49,7 @@ export interface PluginItem {
id: string;
name: string;
site: string;
lang: Language;
lang: string;
version: string;
url: string; // the url of raw code
iconUrl: string;
+3 -3
View File
@@ -4,7 +4,7 @@ import { Appbar, List, SwitchItem } from '@components';
import { useBrowseSettings, usePlugins, useTheme } from '@hooks/persisted';
import { getString } from '@strings/translations';
import { availableLanguages, languages } from '@utils/constants/languages';
import { languages } from '@utils/constants/languages';
import { BrowseSettingsScreenProp } from '@navigators/types';
const BrowseSettings = ({ navigation }: BrowseSettingsScreenProp) => {
@@ -61,10 +61,10 @@ const BrowseSettings = ({ navigation }: BrowseSettingsScreenProp) => {
</>
}
keyExtractor={item => item}
data={availableLanguages}
data={languages}
renderItem={({ item }) => (
<SwitchItem
label={languages[item]}
label={item}
value={languagesFilter.includes(item)}
onPress={() => toggleLanguageFilter(item)}
theme={theme}
+16 -18
View File
@@ -19,7 +19,6 @@ import {
import { coverPlaceholderColor } from '@theme/colors';
import { ThemeColors } from '@theme/types';
import { Swipeable } from 'react-native-gesture-handler';
import { languages } from '@utils/constants/languages';
import { getString } from '@strings/translations';
import { BrowseScreenProps } from '@navigators/types';
import { Button, IconButtonV2 } from '@components';
@@ -30,6 +29,7 @@ import Animated, {
useSharedValue,
withTiming,
} from 'react-native-reanimated';
import { groupBy } from 'lodash-es';
interface AvailableTabProps {
searchText: string;
@@ -168,7 +168,7 @@ export const InstalledTab = memo(
numberOfLines={1}
style={[{ color: theme.onSurfaceVariant }, styles.addition]}
>
{`${languages[item.lang]} - ${item.version}`}
{`${item.lang} - ${item.version}`}
</Text>
</View>
</View>
@@ -297,7 +297,7 @@ const AvailablePluginCard = ({
textStyles,
]}
>
{`${languages[plugin.lang]} - ${plugin.version}`}
{`${plugin.lang} - ${plugin.version}`}
</Animated.Text>
</Animated.View>
</Animated.View>
@@ -337,22 +337,20 @@ export const AvailableTab = memo(({ searchText, theme }: AvailableTabProps) => {
const sections = useMemo(() => {
const list = [];
for (const language of languagesFilter) {
if (filteredAvailablePlugins[language]) {
let plugins;
if (searchText) {
plugins = filteredAvailablePlugins[language]?.filter(plg =>
const group = groupBy(
searchText
? filteredAvailablePlugins.filter(plg =>
plg.name.toLocaleLowerCase().includes(searchText.toLowerCase()),
);
} else {
plugins = filteredAvailablePlugins[language];
}
if (plugins && plugins.length) {
list.push({
header: language,
data: plugins,
});
}
)
: filteredAvailablePlugins,
'lang',
);
for (const language of languagesFilter) {
if (group[language]?.length) {
list.push({
header: language,
data: group[language],
});
}
}
return list;
+16 -37
View File
@@ -2,43 +2,22 @@
// https://en.wikipedia.org/wiki/IETF_language_tag
// https://en.wikipedia.org/wiki/List_of_language_names
export const languages = {
Arabic: 'العربية',
Chinese: '中文, 汉语, 漢語',
English: 'English',
French: 'Français',
Indonesian: 'Bahasa Indonesia',
Japanese: '日本語',
Korean: '조선말, 한국어',
Polish: 'Polski',
Portuguese: 'Português',
Russian: 'Русский',
Spanish: 'Español',
Thai: 'ไทย',
Turkish: 'Türkçe',
Ukrainian: 'Українська',
Vietnamese: 'Tiếng Việt',
} as const;
export type Language = keyof typeof languages;
export type NativeLanguage = (typeof languages)[Language];
export const languagesMapping: Record<string, Language | undefined> = {
'ab': 'Arabic',
'zh': 'Chinese',
export const languagesMapping: Record<string, string> = {
'ab': 'العربية',
'zh': '中文, 汉语, 漢語',
'en': 'English',
'fr': 'French',
'id': 'Indonesian',
'ja': 'Japanese',
'ko': 'Korean',
'pl': 'Polish',
'pt': 'Portuguese',
'ru': 'Russian',
'es': 'Spanish',
'th': 'Thai',
'tr': 'Turkish',
'uk': 'Ukrainian',
'vi': 'Vietnamese',
'fr': 'Français',
'id': 'Bahasa Indonesia',
'ja': '日本語',
'ko': '조선말, 한국어',
'pl': 'Polski',
'pt': 'Português',
'ru': 'Русский',
'es': 'Español',
'th': 'ไทย',
'tr': 'Türkçe',
'uk': 'Українська',
'vi': 'Tiếng Việt',
};
export const availableLanguages = Object.keys(languages) as Language[];
export const languages = Object.values(languagesMapping);