Update plugin sanbox (#1014)
* add htmlparser2 * fix typo * flatten plugins
This commit is contained in:
Generated
+61
-5
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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,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;
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user