@blocklet/list 0.8.14 → 0.8.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/lib/base.js +21 -24
  2. package/lib/components/{aside.js → aside/aside.js} +11 -12
  3. package/lib/components/aside/category-link-list.js +78 -0
  4. package/lib/components/aside/index.js +13 -0
  5. package/lib/components/category-select.js +55 -0
  6. package/lib/components/{button.js → custom-select/button.js} +1 -1
  7. package/lib/components/{custom-select.js → custom-select/custom-select.js} +8 -6
  8. package/lib/components/custom-select/index.js +13 -0
  9. package/lib/components/filter-author.js +2 -3
  10. package/lib/components/{empty.js → list/empty.js} +6 -8
  11. package/lib/components/list/index.js +13 -0
  12. package/lib/components/{list.js → list/list.js} +12 -12
  13. package/lib/components/search.js +21 -19
  14. package/lib/contexts/filter.js +268 -0
  15. package/lib/index.js +6 -16
  16. package/lib/libs/prop-types.js +34 -0
  17. package/lib/{tools → libs}/utils.js +6 -6
  18. package/package.json +60 -64
  19. package/src/base.js +17 -18
  20. package/src/components/{aside.js → aside/aside.js} +7 -8
  21. package/src/components/aside/category-link-list.js +53 -0
  22. package/src/components/aside/index.js +3 -0
  23. package/src/components/category-select.js +43 -0
  24. package/src/components/{button.js → custom-select/button.js} +0 -0
  25. package/src/components/{custom-select.js → custom-select/custom-select.js} +5 -4
  26. package/src/components/custom-select/index.js +3 -0
  27. package/src/components/filter-author.js +2 -3
  28. package/src/components/{empty.js → list/empty.js} +7 -8
  29. package/src/components/list/index.js +3 -0
  30. package/src/components/{list.js → list/list.js} +10 -10
  31. package/src/components/search.js +18 -14
  32. package/src/contexts/filter.js +197 -0
  33. package/src/index.js +6 -16
  34. package/src/libs/prop-types.js +26 -0
  35. package/src/{tools → libs}/utils.js +6 -6
  36. package/lib/components/categories.js +0 -143
  37. package/lib/contexts/store.js +0 -336
  38. package/lib/hooks/page-state.js +0 -69
  39. package/src/components/categories.js +0 -129
  40. package/src/contexts/store.js +0 -266
  41. package/src/hooks/page-state.js +0 -53
@@ -1,266 +0,0 @@
1
- import React, { useContext, createContext, useMemo, useEffect, useState } from 'react';
2
- import PropTypes from 'prop-types';
3
- import { useParams, useHistory } from 'react-router-dom';
4
- import { useRequest } from 'ahooks';
5
- import orderBy from 'lodash-es/orderBy';
6
- import axios from 'axios';
7
- import joinUrl from 'url-join';
8
-
9
- import usePageState from '../hooks/page-state';
10
- import { getCategories, filterBlockletByPrice, replaceTranslate, urlStringify } from '../tools/utils';
11
- import translations from '../assets/locale';
12
-
13
- const axiosInstance = axios.create();
14
- const Search = createContext({});
15
- const { Provider, Consumer } = Search;
16
-
17
- function SearchProvider({ children, baseUrl, type, endpoint, locale, blockletRender }) {
18
- const { location } = window;
19
- const history = useHistory();
20
- const pathParams = useParams();
21
- const isPageMode = type === 'page';
22
- if (isPageMode && !baseUrl) {
23
- throw new Error('baseUrl is required when type is page');
24
- }
25
- const {
26
- data: allBlocklets,
27
- error: fetchBlockletsError,
28
- loading: fetchBlockletsLoading,
29
- run: fetchBlocklets,
30
- } = useRequest(
31
- async () => {
32
- const { data: list } = await axiosInstance.get(joinUrl(endpoint, '/api/blocklets.json'));
33
- return list;
34
- },
35
- { initialData: [], manual: true }
36
- );
37
-
38
- const {
39
- data: allCategories,
40
- error: fetchCategoriesError,
41
- loading: fetchCategoriesLoading,
42
- run: fetchCategories,
43
- } = useRequest(
44
- async () => {
45
- const { data: list } = await axiosInstance.get(`${joinUrl(endpoint, '/api/blocklets/categories')}`);
46
- return list;
47
- },
48
- { initialData: [], manual: true }
49
- );
50
-
51
- const [memoryParams, setMemoryParams] = useState({
52
- sortBy: 'popularity',
53
- sortDirection: 'desc',
54
- });
55
-
56
- const queryParams = useMemo(() => {
57
- return isPageMode ? new URLSearchParams(location.search) : new Map(Object.entries(memoryParams));
58
- }, [memoryParams, location.search]);
59
-
60
- let sortParams;
61
- // 当作页面使用时 sort 数据比较特殊, 默认取 localStorge 中的值,如果 url query 中有sort值则优先使用
62
- if (isPageMode) {
63
- const localSortParams = usePageState(
64
- {
65
- sort: 'popularity',
66
- direction: 'desc',
67
- },
68
- baseUrl
69
- );
70
- const urlSortParams = {
71
- sort: queryParams.get('sortBy'),
72
- direction: queryParams.get('sortDirection'),
73
- };
74
- sortParams = urlSortParams.sort && urlSortParams.direction ? urlSortParams : localSortParams;
75
- } else {
76
- sortParams = useMemo(() => {
77
- return {
78
- sort: memoryParams.sortBy,
79
- direction: memoryParams.sortDirection,
80
- };
81
- }, [memoryParams]);
82
- }
83
-
84
- const isSearchPage = location.pathname === joinUrl(baseUrl, '/search');
85
- const selectedCategory = useMemo(() => {
86
- let result = null;
87
- if (isPageMode) {
88
- result = !isSearchPage ? pathParams.category : queryParams.get('category');
89
- } else {
90
- result = queryParams.get('category');
91
- }
92
- return result;
93
- }, [isPageMode, pathParams, queryParams]);
94
-
95
- const hasDeveloperFilter = !!queryParams.get('developer');
96
- const categoryState = !hasDeveloperFilter
97
- ? { data: allCategories }
98
- : getCategories(allBlocklets, queryParams.get('developer'));
99
-
100
- const blockletList = useMemo(() => {
101
- const sortByName = (x) => x?.title?.toLocaleLowerCase() || x?.name?.toLocaleLowerCase(); // 按名称排序
102
- const sortByPopularity = (x) => x.stats.downloads; // 按下载量排序
103
- const sortByPublish = (x) => x.lastPublishedAt; // 按发布时间
104
- const sortMap = {
105
- nameAsc: sortByName,
106
- nameDesc: sortByName,
107
- popularity: sortByPopularity,
108
- publishAt: sortByPublish,
109
- };
110
-
111
- let result = allBlocklets || [];
112
- // 按照付费/免费筛选
113
- result = filterBlockletByPrice(result, queryParams.get('price'));
114
- // 按照分类筛选
115
- result = result.filter((item) => (selectedCategory ? item?.category?.name === selectedCategory : true));
116
- // 按照作者筛选
117
- result = result.filter((item) => (queryParams?.developer ? item.owner.did === queryParams.get('developer') : true));
118
- const lowerSearch = queryParams?.search?.toLocaleLowerCase() || '';
119
- // 按照搜索筛选
120
- result = result.filter((item) => {
121
- return (
122
- (item?.title || item?.name)?.toLocaleLowerCase().includes(lowerSearch) ||
123
- item.description?.toLocaleLowerCase().includes(lowerSearch) ||
124
- item?.version?.toLocaleLowerCase().includes(lowerSearch)
125
- );
126
- });
127
- // 排序
128
- return orderBy(result, [sortMap[sortParams.sort]], [sortParams.direction]);
129
- }, [allBlocklets, queryParams, sortParams]);
130
-
131
- const categoryList = useMemo(() => {
132
- const list = categoryState.data || [];
133
- // 分类按照名称排序
134
- return orderBy(list, [(i) => i.name], ['asc']);
135
- }, [categoryState.data]);
136
-
137
- const translate = (key, data) => {
138
- if (!translations[locale] || !translations[locale][key]) {
139
- console.warn(`Warning: no ${key} translation of ${locale}`);
140
- return key;
141
- }
142
-
143
- return replaceTranslate(translations[locale][key], data);
144
- };
145
-
146
- const searchStore = {
147
- errors: { fetchBlockletsError, fetchCategoriesError },
148
- loadings: { fetchBlockletsLoading, fetchCategoriesLoading },
149
- endpoint,
150
- sortParams,
151
- history,
152
- blockletList,
153
- t: translate,
154
- queryParams,
155
- selectedCategory,
156
- isSearchPage,
157
- categoryList,
158
- isPageMode,
159
- baseUrl,
160
- blockletRender,
161
- locale,
162
- handleSort: (value) => {
163
- const changData = {
164
- ...Object.fromEntries(queryParams.entries()),
165
- sortBy: value,
166
- sortDirection: value === 'nameAsc' ? 'asc' : 'desc',
167
- };
168
- if (isPageMode) {
169
- sortParams.sort = changData.sortBy;
170
- sortParams.direction = changData.sortDirection;
171
- history.push(`${joinUrl(baseUrl, 'search')}?${urlStringify(changData)}`);
172
- } else {
173
- setMemoryParams(changData);
174
- }
175
- },
176
- handleSearchKeyword: (value) => {
177
- const changData = { ...Object.fromEntries(queryParams.entries()), search: value || undefined };
178
- if (isPageMode) {
179
- history.push(`${joinUrl(baseUrl, 'search')}?${urlStringify(changData)}`);
180
- } else {
181
- setMemoryParams(changData);
182
- }
183
- },
184
- handlePriceFilter: (value) => {
185
- const changData = {
186
- ...Object.fromEntries(queryParams.entries()),
187
- price: value === queryParams.get('price') ? undefined : value,
188
- };
189
- if (isPageMode) {
190
- history.push(`${joinUrl(baseUrl, 'search')}?${urlStringify(changData)}`);
191
- } else {
192
- setMemoryParams(changData);
193
- }
194
- },
195
- getCategoryLocale: (name) => {
196
- if (!name) return null;
197
- let result = null;
198
- const find = categoryState.data.find((item) => item.name === name);
199
- if (find) {
200
- result = find.locales[locale];
201
- }
202
- return result;
203
- },
204
- handleCategorySelect: (value) => {
205
- if (value === 'all') {
206
- const changData = { ...Object.fromEntries(queryParams.entries()), category: undefined };
207
- if (isPageMode) {
208
- history.push(!isSearchPage ? baseUrl : `${joinUrl(baseUrl, 'search')}?${urlStringify(changData)}`);
209
- } else {
210
- setMemoryParams(changData);
211
- }
212
- } else {
213
- const changData = { ...Object.fromEntries(queryParams.entries()), category: value };
214
- if (isPageMode) {
215
- history.push(
216
- !isSearchPage
217
- ? `${joinUrl(baseUrl, 'category', value)}`
218
- : `${joinUrl(baseUrl, 'search')}?${urlStringify(changData)}`
219
- );
220
- } else {
221
- setMemoryParams(changData);
222
- }
223
- }
224
- },
225
- get developerName() {
226
- return allBlocklets.find((i) => i.owner.did === queryParams.get('developer'))?.owner?.name || '';
227
- },
228
- };
229
-
230
- useEffect(() => {
231
- if (!hasDeveloperFilter) {
232
- fetchCategories();
233
- }
234
- }, [!hasDeveloperFilter]);
235
-
236
- useEffect(() => {
237
- fetchBlocklets();
238
- }, [endpoint]);
239
-
240
- return <Provider value={searchStore}>{children}</Provider>;
241
- }
242
-
243
- SearchProvider.propTypes = {
244
- children: PropTypes.any.isRequired,
245
- baseUrl: PropTypes.string,
246
- endpoint: PropTypes.string.isRequired,
247
- // 组件的类型: page 单独作为页面使用 持久化数据将存储在 url 和 localstorage,select 作为选择器组件使用 数据在存储在内存中
248
- type: PropTypes.oneOf(['select', 'page']).isRequired,
249
- locale: PropTypes.oneOf(['zh', 'en']),
250
- blockletRender: PropTypes.func.isRequired,
251
- };
252
-
253
- SearchProvider.defaultProps = {
254
- baseUrl: null,
255
- locale: 'zh',
256
- };
257
-
258
- function useSearchContext() {
259
- const searchStore = useContext(Search);
260
- if (!searchStore) {
261
- return {};
262
- }
263
- return searchStore;
264
- }
265
-
266
- export { SearchProvider, Consumer as SearchConsumer, useSearchContext };
@@ -1,53 +0,0 @@
1
- import { useLocalStorageState, useReactive } from 'ahooks';
2
-
3
- const PAGE_STATE_KEY = 'page-state';
4
-
5
- export default function usePageState(defaultValue = {}, persistence = true, path = window.location.pathname) {
6
- const [pageState, setPageState] = useLocalStorageState(PAGE_STATE_KEY, {});
7
- const state = useReactive(pageState);
8
- if (!(path in pageState) && persistence) {
9
- state[path] = defaultValue;
10
- syncState();
11
- }
12
- function syncState() {
13
- setPageState(state);
14
- }
15
-
16
- return new Proxy(
17
- {},
18
- {
19
- get: (target, prop) => {
20
- try {
21
- return state[path][prop];
22
- } catch {
23
- return undefined;
24
- }
25
- },
26
- set: (target, prop, value) => {
27
- try {
28
- const data = {
29
- ...(state[path] || {}),
30
- [prop]: value,
31
- };
32
- state[path] = data;
33
- syncState();
34
- return true;
35
- } catch {
36
- return false;
37
- }
38
- },
39
- deleteProperty: (target, prop) => {
40
- try {
41
- const data = { ...(state[path] || {}) };
42
- delete data[prop];
43
- delete state[path][prop];
44
- syncState();
45
- return true;
46
- } catch {
47
- return false;
48
- }
49
- },
50
- ownKeys: () => Object.keys(state[path] || {}),
51
- }
52
- );
53
- }