@zhang_libo/resource-hub 1.0.2
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.
- package/LICENSE +21 -0
- package/README.en.md +80 -0
- package/README.ja.md +80 -0
- package/README.md +79 -0
- package/README.zh-TW.md +80 -0
- package/bin/cli.js +10 -0
- package/dist/app.d.ts +2 -0
- package/dist/app.d.ts.map +1 -0
- package/dist/app.js +59 -0
- package/dist/app.js.map +1 -0
- package/dist/db/index.js +12 -0
- package/dist/db/index.js.map +1 -0
- package/dist/db/migrate.d.ts +3 -0
- package/dist/db/migrate.d.ts.map +1 -0
- package/dist/db/migrate.js +169 -0
- package/dist/db/migrate.js.map +1 -0
- package/dist/db/schema.d.ts +743 -0
- package/dist/db/schema.d.ts.map +1 -0
- package/dist/db/schema.js +88 -0
- package/dist/db/schema.js.map +1 -0
- package/dist/i18n.js +309 -0
- package/dist/i18n.js.map +1 -0
- package/dist/plugins/admin.d.ts +4 -0
- package/dist/plugins/admin.d.ts.map +1 -0
- package/dist/plugins/admin.js +19 -0
- package/dist/plugins/admin.js.map +1 -0
- package/dist/plugins/auth.d.ts +4 -0
- package/dist/plugins/auth.d.ts.map +1 -0
- package/dist/plugins/auth.js +35 -0
- package/dist/plugins/auth.js.map +1 -0
- package/dist/routes/auth.d.ts +4 -0
- package/dist/routes/auth.d.ts.map +1 -0
- package/dist/routes/auth.js +352 -0
- package/dist/routes/auth.js.map +1 -0
- package/dist/routes/categories.d.ts +4 -0
- package/dist/routes/categories.d.ts.map +1 -0
- package/dist/routes/categories.js +112 -0
- package/dist/routes/categories.js.map +1 -0
- package/dist/routes/config.d.ts +4 -0
- package/dist/routes/config.d.ts.map +1 -0
- package/dist/routes/config.js +227 -0
- package/dist/routes/config.js.map +1 -0
- package/dist/routes/resources.d.ts +4 -0
- package/dist/routes/resources.d.ts.map +1 -0
- package/dist/routes/resources.js +474 -0
- package/dist/routes/resources.js.map +1 -0
- package/dist/routes/tags.d.ts +4 -0
- package/dist/routes/tags.d.ts.map +1 -0
- package/dist/routes/tags.js +37 -0
- package/dist/routes/tags.js.map +1 -0
- package/dist/routes/users.d.ts +4 -0
- package/dist/routes/users.d.ts.map +1 -0
- package/dist/routes/users.js +181 -0
- package/dist/routes/users.js.map +1 -0
- package/dist/services/crypto.js +49 -0
- package/dist/services/crypto.js.map +1 -0
- package/dist/services/mail.d.ts +16 -0
- package/dist/services/mail.d.ts.map +1 -0
- package/dist/services/mail.js +33 -0
- package/dist/services/mail.js.map +1 -0
- package/dist/services/rsa.js +49 -0
- package/dist/services/rsa.js.map +1 -0
- package/dist/services/token.d.ts +9 -0
- package/dist/services/token.d.ts.map +1 -0
- package/dist/services/token.js +29 -0
- package/dist/services/token.js.map +1 -0
- package/dist/types.d.ts +80 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +73 -0
- package/public/admin/AdminCategories.jsx +310 -0
- package/public/admin/AdminConfig.jsx +254 -0
- package/public/admin/AdminEmail.jsx +279 -0
- package/public/admin/AdminTags.jsx +263 -0
- package/public/admin/AdminUsers.jsx +452 -0
- package/public/app.jsx +186 -0
- package/public/components/ConfirmDialog.jsx +78 -0
- package/public/components/DropdownSelect.jsx +281 -0
- package/public/components/EmailPreviewModal.jsx +104 -0
- package/public/components/EmptyState.jsx +50 -0
- package/public/components/Modal.jsx +127 -0
- package/public/components/PasswordStrength.jsx +45 -0
- package/public/components/Skeleton.jsx +68 -0
- package/public/components/Toast.jsx +80 -0
- package/public/components/TooltipIconButton.jsx +55 -0
- package/public/context/AppContext.jsx +314 -0
- package/public/features/BatchResourceModal.jsx +606 -0
- package/public/features/ChangePasswordModal.jsx +187 -0
- package/public/features/ProfileModal.jsx +170 -0
- package/public/features/ResourceCard.jsx +422 -0
- package/public/features/ResourceFormModal.jsx +915 -0
- package/public/features/ResourceRow.jsx +287 -0
- package/public/features/ResourceTimeline.jsx +472 -0
- package/public/hooks/useApi.jsx +26 -0
- package/public/hooks/useRouter.jsx +35 -0
- package/public/index.html +258 -0
- package/public/layout/AdminLayout.jsx +167 -0
- package/public/layout/AppLayout.jsx +119 -0
- package/public/layout/AuthLayout.jsx +503 -0
- package/public/layout/Header.jsx +543 -0
- package/public/layout/Sidebar.jsx +175 -0
- package/public/pages/AdminPage.jsx +30 -0
- package/public/pages/ForgotPasswordPage.jsx +93 -0
- package/public/pages/HomePage.jsx +2297 -0
- package/public/pages/LoginPage.jsx +191 -0
- package/public/pages/RegisterPage.jsx +137 -0
- package/public/pages/ResetPasswordPage.jsx +169 -0
- package/public/pages/SetupPage.jsx +157 -0
- package/public/utils/helpers.jsx +152 -0
- package/public/utils/i18n.jsx +1374 -0
- package/public/utils/preferences.jsx +220 -0
- package/public/utils/security.jsx +88 -0
- package/public/utils/theme.jsx +24 -0
- package/public/vendor/babel.min.js +2 -0
- package/public/vendor/lucide-react.min.js +9 -0
- package/public/vendor/react-dom.development.js +29869 -0
- package/public/vendor/react.development.js +3342 -0
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
// Helper utilities
|
|
2
|
+
const COLOR_POOL = ['#4F46E5', '#8B5CF6', '#F59E0B', '#14B8A6', '#F43F5E'];
|
|
3
|
+
const CATEGORY_TONES = [
|
|
4
|
+
{ key: 'development', accent: '#4F46E5', soft: '#EEF2FF', softStrong: '#DCE4FF', border: '#D5DCFF' },
|
|
5
|
+
{ key: 'design', accent: '#8B5CF6', soft: '#F3EDFF', softStrong: '#E9DEFF', border: '#E6D8FF' },
|
|
6
|
+
{ key: 'productivity', accent: '#F59E0B', soft: '#FFF6E7', softStrong: '#FDECCB', border: '#F4DEB2' },
|
|
7
|
+
{ key: 'knowledge', accent: '#14B8A6', soft: '#E9FBF8', softStrong: '#D5F5F1', border: '#BFEDE6' },
|
|
8
|
+
{ key: 'ai', accent: '#F43F5E', soft: '#FFF0F4', softStrong: '#FFDDE7', border: '#FFD3DE' },
|
|
9
|
+
];
|
|
10
|
+
const CATEGORY_KEYWORDS = {
|
|
11
|
+
development: ['开发', '编程', '代码', '前端', '后端', '工程', 'api', 'dev', 'code', 'github', 'vscode', 'postman'],
|
|
12
|
+
design: ['设计', '视觉', 'ui', 'ux', 'figma', 'dribbble', 'sketch'],
|
|
13
|
+
productivity: ['效率', '办公', '协作', '笔记', 'notion', 'obsidian', 'calendar'],
|
|
14
|
+
knowledge: ['文档', '知识', '学习', '教程', '资料', 'docs', 'mdn', 'wiki'],
|
|
15
|
+
ai: ['ai', '智能', '模型', 'gpt', 'chatgpt', 'llm'],
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
function hashSeed(seed) {
|
|
19
|
+
const value = String(seed || 'resourcehub');
|
|
20
|
+
let hash = 0;
|
|
21
|
+
|
|
22
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
23
|
+
hash = ((hash << 5) - hash + value.charCodeAt(index)) >>> 0;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return hash;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function matchCategoryToneKey(name) {
|
|
30
|
+
const normalized = String(name || '').trim().toLowerCase();
|
|
31
|
+
if (!normalized) return null;
|
|
32
|
+
|
|
33
|
+
return Object.entries(CATEGORY_KEYWORDS)
|
|
34
|
+
.find(([, keywords]) => keywords.some((keyword) => normalized.includes(keyword.toLowerCase())))?.[0] || null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function getCategoryTone(category, fallbackSeed = '') {
|
|
38
|
+
if (category && typeof category === 'object' && category.tone?.accent) {
|
|
39
|
+
return category.tone;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const categoryName = typeof category === 'string' ? category : category?.name || '';
|
|
43
|
+
const toneKey = matchCategoryToneKey(categoryName);
|
|
44
|
+
|
|
45
|
+
if (toneKey) {
|
|
46
|
+
return CATEGORY_TONES.find((tone) => tone.key === toneKey) || CATEGORY_TONES[0];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const seed = typeof category === 'object'
|
|
50
|
+
? `${category?.id ?? ''}:${categoryName}:${fallbackSeed}`
|
|
51
|
+
: `${categoryName}:${fallbackSeed}`;
|
|
52
|
+
|
|
53
|
+
return CATEGORY_TONES[hashSeed(seed) % CATEGORY_TONES.length];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function getCategoryAccent(category, fallbackSeed = '') {
|
|
57
|
+
return getCategoryTone(category, fallbackSeed).accent;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function getLogoFallbackColor(name, category) {
|
|
61
|
+
return getCategoryAccent(category, name);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function formatDate(timestamp) {
|
|
65
|
+
if (!timestamp) return '';
|
|
66
|
+
|
|
67
|
+
let seconds = timestamp;
|
|
68
|
+
if (seconds > 1e12) {
|
|
69
|
+
seconds = Math.floor(seconds / 1000);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const date = new Date(seconds * 1000);
|
|
73
|
+
const now = new Date();
|
|
74
|
+
const locale = window.i18n?.getCurrentLocale?.() || 'zh-Hans';
|
|
75
|
+
const intlLocale = window.i18n?.getIntlLocale?.(locale) || 'zh-CN';
|
|
76
|
+
|
|
77
|
+
if (date.toDateString() === now.toDateString()) {
|
|
78
|
+
return window.i18n?.translateText?.(locale, '今天') || '今天';
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return date.toLocaleDateString(intlLocale, { year: 'numeric', month: 'long', day: 'numeric' });
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function formatMonth(timestamp) {
|
|
85
|
+
if (!timestamp) return '';
|
|
86
|
+
const date = new Date(timestamp * 1000);
|
|
87
|
+
const locale = window.i18n?.getCurrentLocale?.() || 'zh-Hans';
|
|
88
|
+
const intlLocale = window.i18n?.getIntlLocale?.(locale) || 'zh-CN';
|
|
89
|
+
return date.toLocaleDateString(intlLocale, { year: 'numeric', month: 'long' });
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function truncate(str, maxLen) {
|
|
93
|
+
if (!str) return '';
|
|
94
|
+
if (str.length <= maxLen) return str;
|
|
95
|
+
return str.slice(0, maxLen) + '\u2026';
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function getDomain(url) {
|
|
99
|
+
try {
|
|
100
|
+
return new URL(url).hostname;
|
|
101
|
+
} catch {
|
|
102
|
+
return url;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function recordResourceVisit({ resource, request, dispatch }) {
|
|
107
|
+
if (!resource?.id || typeof request !== 'function') return Promise.resolve();
|
|
108
|
+
|
|
109
|
+
return request(`/api/resources/${resource.id}/visit`, { method: 'POST' })
|
|
110
|
+
.then(({ ok, data }) => {
|
|
111
|
+
if (!ok) return;
|
|
112
|
+
|
|
113
|
+
const nextVisitCount = data?.data?.visitCount;
|
|
114
|
+
if (typeof nextVisitCount === 'number' && dispatch) {
|
|
115
|
+
dispatch({
|
|
116
|
+
type: 'SET_RESOURCE_VISIT_COUNT',
|
|
117
|
+
id: resource.id,
|
|
118
|
+
visitCount: nextVisitCount,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
window.dispatchEvent(new CustomEvent('rh:analytics-invalidated'));
|
|
123
|
+
})
|
|
124
|
+
.catch(() => {});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function useViewportWidth() {
|
|
128
|
+
const [width, setWidth] = React.useState(() => window.innerWidth);
|
|
129
|
+
|
|
130
|
+
React.useEffect(() => {
|
|
131
|
+
const handleResize = () => setWidth(window.innerWidth);
|
|
132
|
+
window.addEventListener('resize', handleResize);
|
|
133
|
+
return () => window.removeEventListener('resize', handleResize);
|
|
134
|
+
}, []);
|
|
135
|
+
|
|
136
|
+
return width;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
window.helpers = {
|
|
140
|
+
COLOR_POOL,
|
|
141
|
+
CATEGORY_TONES,
|
|
142
|
+
getCategoryTone,
|
|
143
|
+
getCategoryAccent,
|
|
144
|
+
getLogoFallbackColor,
|
|
145
|
+
formatDate,
|
|
146
|
+
formatMonth,
|
|
147
|
+
truncate,
|
|
148
|
+
getDomain,
|
|
149
|
+
recordResourceVisit,
|
|
150
|
+
useViewportWidth,
|
|
151
|
+
};
|
|
152
|
+
window.useViewportWidth = useViewportWidth;
|