feat: add new help page showing learning resources

This commit is contained in:
linonetwo 2023-12-25 22:44:48 +08:00
parent 38d86f96ea
commit 717fa07575
12 changed files with 166 additions and 5 deletions

View file

@ -3,6 +3,7 @@
"WorkspaceSelector": {
"Add": "Add",
"Guide": "Guide",
"Help": "Help",
"OpenWorkspaceTagTiddler": "Open {{tagName}}",
"DefaultTiddlers": "Default Tiddlers",
"OpenWorkspaceMenuName": "Open Workspace",

View file

@ -4,6 +4,7 @@
"WorkspaceSelector": {
"Add": "添加",
"Guide": "引导",
"Help": "帮助",
"OpenWorkspaceTagTiddler": "打开 {{tagName}}",
"DefaultTiddlers": "默认条目",
"OpenWorkspaceMenuName": "打开工作区",

View file

@ -7,7 +7,7 @@ import { useLocation } from 'wouter';
import { getBuildInPageName } from '@services/pages/getBuildInPageName';
import { WindowNames } from '@services/windows/WindowProperties';
import { getBuildInPageIcon } from './getBuildInPageIcon';
import { getBuildInPageIcon } from '../../services/pages/getBuildInPageIcon';
import { PageSelectorBase } from './PageSelectorBase';
export interface ISortableItemProps {

View file

@ -0,0 +1,77 @@
/* eslint-disable unicorn/no-null */
import { Button, Menu, MenuItem } from '@mui/material';
import { WindowNames } from '@services/windows/WindowProperties';
import React, { useState } from 'react';
import { styled } from 'styled-components';
const StyledGridItem = styled.div`
// Add styles for your grid item here
`;
interface HelpWebsiteItemProps {
item: {
description: string;
fallbackUrls: string[];
title: string;
url: string;
};
}
export const HelpWebsiteItem: React.FC<HelpWebsiteItemProps> = ({ item }) => {
const [anchorElement, setAnchorElement] = useState<null | HTMLElement>(null);
const openExternalLink = (uri: string) => {
void window.service.window.open(WindowNames.any, { uri });
};
const handleOpenMenu = (event: React.MouseEvent<HTMLButtonElement>) => {
setAnchorElement(event.currentTarget);
};
const handleCloseMenu = () => {
setAnchorElement(null);
};
return (
<StyledGridItem>
<h3>{item.title}</h3>
<p>{item.description}</p>
<Button
onClick={() => {
openExternalLink(item.url);
}}
>
Open
</Button>
{item.fallbackUrls.length > 0 && (
<>
<Button
aria-controls='fallback-menu'
aria-haspopup='true'
onClick={handleOpenMenu}
>
Alternatives
</Button>
<Menu
id='fallback-menu'
anchorEl={anchorElement}
keepMounted
open={Boolean(anchorElement)}
onClose={handleCloseMenu}
>
{item.fallbackUrls.map((url, index) => (
<MenuItem
key={index}
onClick={() => {
openExternalLink(url);
}}
>
{new URL(url).hostname}
</MenuItem>
))}
</Menu>
</>
)}
</StyledGridItem>
);
};

View file

@ -0,0 +1,10 @@
{
"onlineSources": [
"tidgi.fun/recipes/default/tiddlers.json?filter=[tag[HelpPage]]"
],
"default": [
{"title":"Tiddlywiki XP", "description": "「一个快速体验太微的机会」—— 非常全面、易读,包括了很多非常基础的操作,例如「如何新建第一个笔记」","tags":"Intro","url":"https://keatonlao.gitee.io/tiddlywiki-xp/", "fallbackUrls": "https://keatonlao.github.io/tiddlywiki-xp/", "language": "zh-Hans"},
{"title":"Grok TiddlyWiki", "description": "Build a deep, lasting understanding of TiddlyWiki","tags":"Intro FAQ","url":"https://groktiddlywiki.com/read/", "language": "en-GB"},
{"title":"太微中文教程", "description": "中文社区共建的TiddlyWiki教程体验从入门到知识管理大师之路","tags":"Intro","url":"https://tw-cn.netlify.app/", "fallbackUrls": "https://tw-cn.cpolar.top/ https://tiddly-wiki-chinese-tutorial.vercel.app/ https://tiddly-gittly.github.io/TiddlyWiki-Chinese-Tutorial/", "language": "zh-Hans"}
]
}

16
src/pages/Help/index.tsx Normal file
View file

@ -0,0 +1,16 @@
import { Grid } from '@mui/material';
import { HelpWebsiteItem } from './HelpWebsiteItem';
import { useLoadHelpPagesList } from './useLoadHelpPagesList';
export function Help(): JSX.Element {
const items = useLoadHelpPagesList();
return (
<Grid container spacing={2}>
{items.map((item, index) => (
<Grid key={index} item xs={12} sm={6} md={4}>
<HelpWebsiteItem item={item} />
</Grid>
))}
</Grid>
);
}

View file

@ -0,0 +1,38 @@
/* eslint-disable unicorn/no-array-callback-reference */
import uniqBy from 'lodash/uniqBy';
import { useEffect, useState } from 'react';
import { LastArrayElement } from 'type-fest';
import helpPages from './helpPages.json';
function makeFallbackUrlsArray(item: LastArrayElement<typeof helpPages.default>): Omit<LastArrayElement<typeof helpPages.default>, 'fallbackUrls'> & { fallbackUrls: string[] } {
return { ...item, fallbackUrls: item?.fallbackUrls?.split(' ') ?? [] };
}
export function useLoadHelpPagesList() {
const [items, setItems] = useState(helpPages.default.map(makeFallbackUrlsArray));
useEffect(() => {
const loadMoreItems = async () => {
try {
const responses = await Promise.all(
helpPages.onlineSources.map(async source => {
try {
const data = await fetch(source).then(async response => await (response.json() as Promise<typeof helpPages.default>));
return data.map(makeFallbackUrlsArray);
} catch (error) {
await window.service.native.log('error', `Help page Failed to load online source: ${source} ${(error as Error).message}`);
return [];
}
}),
);
const newItems = responses.flat();
setItems(currentItems => uniqBy([...currentItems, ...newItems], 'url'));
} catch (error) {
console.error('Failed to load online sources:', error);
}
};
void loadMoreItems();
}, []);
return items;
}

View file

@ -1,9 +1,8 @@
/* eslint-disable @typescript-eslint/strict-boolean-expressions */
/* eslint-disable @typescript-eslint/promise-function-async */
import { lazy } from 'react';
import { Helmet } from 'react-helmet';
import { useTranslation } from 'react-i18next';
import styled, { DefaultTheme } from 'styled-components';
import { DefaultTheme, styled } from 'styled-components';
import is, { isNot } from 'typescript-styled-is';
import { Route, Switch } from 'wouter';
@ -13,6 +12,7 @@ import { WindowNames } from '@services/windows/WindowProperties';
import FindInPage from '../../components/FindInPage';
import { SideBar } from '../../components/Sidebar';
import { Guide } from '../Guide';
import { Help } from '../Help';
import { WikiBackground } from '../WikiBackground';
import { useInitialPage } from './useInitialPage';
@ -79,6 +79,7 @@ export default function Main(): JSX.Element {
<Switch>
<Route path={`/${WindowNames.main}/${PageType.wiki}/:id/`} component={WikiBackground} />
<Route path={`/${WindowNames.main}/${PageType.guide}/`} component={Guide} />
<Route path={`/${WindowNames.main}/${PageType.help}/`} component={Help} />
<Route path={`/${WindowNames.main}`} component={Guide} />
<Route component={Guide} />
</Switch>

View file

@ -4,11 +4,18 @@ import { IPage, PageType } from './interface';
* Add React component route for build-in pages in `src/pages/Main/index.tsx`
*/
export const defaultBuildInPages: Record<string, IPage> = {
help: {
type: PageType.help,
id: PageType.help,
active: false,
hide: false,
order: 1,
},
guide: {
type: PageType.guide,
id: PageType.guide,
active: false,
hide: false,
order: 0,
order: 2,
},
};

View file

@ -1,4 +1,4 @@
import AccountTreeIcon from '@mui/icons-material/AccountTree';
import HelpIcon from '@mui/icons-material/Help';
import InfoIcon from '@mui/icons-material/Info';
import { PageType } from '@services/pages/interface';
@ -8,6 +8,9 @@ export function getBuildInPageIcon(pageType: PageType): JSX.Element {
// this won't happened, because wiki page is not a build-in page
return <div>Wiki</div>;
}
case PageType.help: {
return <HelpIcon />;
}
case PageType.guide: {
return <InfoIcon />;
}

View file

@ -6,6 +6,9 @@ export function getBuildInPageName(pageType: PageType, t: TFunction) {
case PageType.wiki: {
return t('Menu.Wiki');
}
case PageType.help: {
return t('WorkspaceSelector.Help');
}
case PageType.guide: {
return t('WorkspaceSelector.Guide');
}

View file

@ -7,6 +7,10 @@ export enum PageType {
* Default empty page, have some user guide and new user settings.
*/
guide = 'guide',
/**
* Show list of available help resources to learn TiddlyWiki.
*/
help = 'help',
/**
* All "workspaces". It is hard to merge workspace concept with page concept, because will need to migrate all user data. So we leave them to be still workspace, but also call them wiki pages. And in event listeners about wiki page, we redirect them to call workspace methods.
*/