refactor: improve SEO and a10y

This commit is contained in:
Luyu Cheng
2023-04-24 18:21:36 +08:00
parent 23b296b3b9
commit 33948a73e5
19 changed files with 107 additions and 40 deletions

View File

@@ -1,6 +1,7 @@
<!DOCTYPE html>
<html lang="zh-Hans">
<head>
<meta charset="UTF-8">
<script async src="https://www.googletagmanager.com/gtag/js?id=G-3FTEQXFKNF"></script>
<script>
window.dataLayer = window.dataLayer || []

View File

@@ -153,11 +153,11 @@ const Footer: React.FC = () => {
)}
<footer className="mt-4 flex w-full items-center justify-center pb-1 text-sm ease-in" onClick={(e) => e.currentTarget.blur()}>
<a href="https://github.com/Kaiyiwing/qwerty-learner" target="_blank" rel="noreferrer">
<a href="https://github.com/Kaiyiwing/qwerty-learner" target="_blank" rel="noreferrer" aria-label="前往 GitHub 项目主页">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 496 512"
className="mr-3 inline h-4 w-4 fill-current text-gray-500 focus:outline-none dark:text-gray-400"
className="mr-3 inline h-4 w-4 fill-current text-gray-500 focus:outline-none dark:text-gray-400"
>
<path d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z" />
</svg>
@@ -169,8 +169,9 @@ const Footer: React.FC = () => {
handleOpenInfoPanel('redBook')
e.currentTarget.blur()
}}
aria-label="加入我们的小红书社群"
>
<img src={redBookLogo} className="fill-current text-gray-500" alt="red book" />
<img src={redBookLogo} width={16} height={16} className="fill-current text-gray-500" alt="red book" />
</button>
<button
className="cursor-pointer focus:outline-none "
@@ -179,6 +180,7 @@ const Footer: React.FC = () => {
handleOpenInfoPanel('community')
e.currentTarget.blur()
}}
aria-label="加入我们的微信用户群"
>
<svg
xmlns="http://www.w3.org/2000/svg"
@@ -196,6 +198,7 @@ const Footer: React.FC = () => {
handleOpenInfoPanel('donate')
e.currentTarget.blur()
}}
aria-label="考虑捐赠我们"
>
<svg
xmlns="http://www.w3.org/2000/svg"
@@ -213,16 +216,23 @@ const Footer: React.FC = () => {
handleOpenInfoPanel('vsc')
e.currentTarget.blur()
}}
aria-label="使用 Visual Studio Code 插件版 Qwerty Learner"
>
<img src={vscLogo} className="fill-current text-gray-500" alt="visual studio code" />
<img src={vscLogo} width={14} height={14} className="fill-current text-gray-500" alt="Visual Studio Code" />
</button>
<a href="mailto:me@kaiyi.cool" target="_blank" rel="noreferrer" onClick={(e) => e.currentTarget.blur()}>
<a
href="mailto:me@kaiyi.cool"
target="_blank"
rel="noreferrer"
onClick={(e) => e.currentTarget.blur()}
aria-label="发送邮件到 me@kaiyi.cool"
>
<EnvelopeIcon className="mr-3 inline h-4 w-4 text-gray-500 dark:text-gray-400" />
</a>
<Tooltip content="中国大陆镜像">
<a href="https://kaiyiwing.gitee.io/qwerty-learner" target="_self">
<img src={cnFlag} className="mr-2 h-5 w-5 cursor-pointer" />
<a href="https://kaiyiwing.gitee.io/qwerty-learner" target="_self" title="前往中国大陆镜像">
<img src={cnFlag} className="mr-2 h-5 w-5 cursor-pointer" alt="中国国旗" />
</a>
</Tooltip>
<button
@@ -243,7 +253,7 @@ const Footer: React.FC = () => {
>
ICP备2022030649号
</a>
<span className="ml-2 select-none rounded bg-gray-200 px-1 py-0.5 text-xs text-gray-400 dark:bg-gray-800 dark:text-gray-500">
<span className="ml-2 select-none rounded bg-slate-200 px-1 py-0.5 text-xs text-slate-600 dark:bg-slate-800 dark:text-slate-400">
Build <span className="select-all">{LATEST_COMMIT_HASH}</span>
</span>
</footer>

View File

@@ -7,10 +7,10 @@ const Header: React.FC<PropsWithChildren> = ({ children }) => {
<header className="container mx-auto w-full px-10 py-6">
<div className="flex w-full flex-col items-center justify-between space-y-3 lg:flex-row lg:space-y-0">
<NavLink
className="flex items-center text-2xl font-bold text-indigo-400 no-underline hover:no-underline lg:text-4xl"
className="flex items-center text-2xl font-bold text-indigo-500 no-underline hover:no-underline lg:text-4xl"
to="https://qwerty.kaiyi.cool/"
>
<img src={logo} className="mr-3 h-16 w-16" />
<img src={logo} className="mr-3 h-16 w-16" alt="Qwerty Learner Logo" />
<h1>Qwerty Learner</h1>
</NavLink>
<nav className="card on element flex w-auto content-center items-center justify-end space-x-3 rounded-xl bg-white p-4 transition-colors duration-300 dark:bg-gray-800">

View File

@@ -63,8 +63,10 @@ export default function StarCard() {
) : (
<div className="flex pb-0 pt-6">
<button
onClick={onClickWantStar}
className="rounded-lg bg-indigo-600 px-6 py-2 text-lg text-white transition-colors duration-300 focus:outline-none"
type="button"
onClick={onClickWantStar}
title="我想收藏"
>
</button>
@@ -94,7 +96,7 @@ export default function StarCard() {
</span>
)}
<button type="button" onClick={onClickCloseStar}>
<button type="button" onClick={onClickCloseStar} title="关闭提示" aria-label="关闭提示">
<IconCircleX className="text-indigo-400" />
</button>
</div>

View File

@@ -27,9 +27,9 @@ body,
}
.card {
box-shadow: 0px 100px 80px rgba(0, 0, 0, 0.07), 0px 41.7776px 33.4221px rgba(0, 0, 0, 0.0503198),
0px 22.3363px 17.869px rgba(0, 0, 0, 0.0417275), 0px 12.5216px 10.0172px rgba(0, 0, 0, 0.035),
0px 6.6501px 5.32008px rgba(0, 0, 0, 0.0282725), 0px 2.76726px 2.21381px rgba(0, 0, 0, 0.0196802);
box-shadow: 0px 100px 80px rgba(50, 46, 129, 0.07), 0px 41.7776px 33.4221px rgba(50, 46, 129, 0.0503198),
0px 22.3363px 17.869px rgba(50, 46, 129, 0.0417275), 0px 12.5216px 10.0172px rgba(50, 46, 129, 0.035),
0px 6.6501px 5.32008px rgba(50, 46, 129, 0.0282725), 0px 2.76726px 2.21381px rgba(50, 46, 129, 0.0196802);
}
/* Well, TailwindCSS doest have `text-shadow` classes. */

View File

@@ -39,7 +39,7 @@ export default function DictRequest() {
<br />
<br />
{' '}
<a href="mailto:me@kaiyi.cool" className="px-2 text-blue-500">
<a href="mailto:me@kaiyi.cool" className="px-2 text-blue-500" aria-label="发送邮件到 me@kaiyi.cool">
me@kaiyi.cool
</a>

View File

@@ -8,7 +8,7 @@ interface Props {
function Dictionary({ dictionary, onClick }: Props) {
return (
<div className="flex h-40 w-80 items-center justify-center" onClick={onClick}>
<div className="flex h-40 w-80 items-center justify-center" role="button" onClick={onClick} title="选择词典">
<div className="h-full w-5/12 rounded-xl bg-gray-300">
<div className="bg-gray-700"></div>
</div>

View File

@@ -24,7 +24,9 @@ export const ChapterButton: React.FC<ChapterButtonProps> = ({ index, selected, w
<button
ref={buttonRef}
className="relative flex h-28 w-44 flex-col items-start justify-start overflow-hidden rounded-md border border-gray-300 bg-gray-50 p-4 text-left shadow-lg focus:outline-none dark:border-gray-500 dark:bg-gray-700 dark:bg-opacity-10"
type="button"
onClick={onClick}
title="选择章节"
>
<p className="w-full pb-2 text-lg text-gray-800 dark:text-white dark:text-opacity-80">Chapter {index + 1}</p>
<p className="text-xs font-medium text-gray-600 dark:text-white dark:text-opacity-60">: {wordCount}</p>

View File

@@ -22,10 +22,12 @@ const DictionaryCard: React.FC<DictionaryCardProps> = ({ dictionary }) => {
<button
ref={buttonRef}
className="relative w-48 overflow-hidden rounded-md border border-gray-300 bg-gray-50 p-4 text-left shadow-lg focus:outline-none dark:border-gray-500 dark:bg-gray-700 dark:bg-opacity-10 "
type="button"
onClick={() => {
setCurrentDictId(dictionary.id)
setCurrentChapter(0)
}}
title="选择词典"
>
<p className="mb-1 text-xl text-gray-800 dark:text-white dark:text-opacity-80">{dictionary.name}</p>
<p className="mb-1 text-xs text-gray-900 dark:text-white dark:text-opacity-90">{dictionary.description}</p>

View File

@@ -98,7 +98,7 @@ const PronunciationSwitcher = () => {
{({ open }) => (
<>
<Popover.Button
className={`flex h-8 cursor-pointer items-center justify-center rounded-md px-1 transition-colors duration-300 ease-in-out hover:bg-indigo-400 hover:text-white focus:outline-none dark:text-white dark:text-opacity-60 dark:hover:text-opacity-100 ${
className={`flex h-8 cursor-pointer items-center justify-center rounded-md px-1 transition-colors duration-300 ease-in-out hover:bg-indigo-400 hover:text-white focus:outline-none dark:text-white dark:text-opacity-60 dark:hover:text-opacity-100 ${
open ? 'bg-indigo-400 text-white' : 'bg-transparent'
}`}
onFocus={(e) => {

View File

@@ -22,9 +22,16 @@ export default function WordChip({ word }: { word: Word }) {
return (
<>
<div ref={refs.setReference} className="word-chip select-none" {...getReferenceProps()} onClick={onClickWord}>
<button
ref={refs.setReference}
className="word-chip select-none"
{...getReferenceProps()}
type="button"
onClick={onClickWord}
title={`朗读 ${word.name}`}
>
<span>{word.name}</span>
</div>
</button>
{showTranslation && (
<div
ref={refs.setFloating}

View File

@@ -182,6 +182,7 @@ const ResultScreen = () => {
}}
className="cursor-pointer text-gray-500 dark:text-gray-400"
type="button"
title="加入我们的社区"
>
<svg
xmlns="http://www.w3.org/2000/svg"
@@ -207,7 +208,9 @@ const ResultScreen = () => {
<Tooltip content="快捷键shift + enter">
<button
className="btn-primary h-12 border-2 border-solid border-gray-300 bg-white text-base text-gray-700 dark:border-gray-700 dark:bg-gray-600 dark:text-white dark:hover:bg-gray-700"
type="button"
onClick={dictationButtonHandler}
title="默写本章节"
>
</button>
@@ -215,7 +218,9 @@ const ResultScreen = () => {
<Tooltip content="快捷键space">
<button
className="btn-primary h-12 border-2 border-solid border-gray-300 bg-white text-base text-gray-700 dark:border-gray-700 dark:bg-gray-600 dark:text-white dark:hover:bg-gray-700"
type="button"
onClick={repeatButtonHandler}
title="重复本章节"
>
</button>
@@ -224,7 +229,9 @@ const ResultScreen = () => {
<Tooltip content="快捷键enter">
<button
className={`btn-primary { isLastChapter ? 'cursor-not-allowed opacity-50' : ''} h-12 text-base font-bold `}
type="button"
onClick={nextButtonHandler}
title="下一章节"
>
</button>

View File

@@ -73,7 +73,13 @@ export default function DataSetting() {
<span className="ml-4 w-10 text-xs font-normal text-gray-600">{`${exportProgress}%`}</span>
</div>
<button className="btn-primary ml-4 disabled:bg-gray-300" onClick={onClickExport} disabled={isExporting}>
<button
className="btn-primary ml-4 disabled:bg-gray-300"
type="button"
onClick={onClickExport}
disabled={isExporting}
title="导出数据"
>
</button>
</div>
@@ -96,7 +102,13 @@ export default function DataSetting() {
<span className="ml-4 w-10 text-xs font-normal text-gray-600">{`${importProgress}%`}</span>
</div>
<button className="btn-primary ml-4 disabled:bg-gray-300" onClick={onClickImport} disabled={isImporting}>
<button
className="btn-primary ml-4 disabled:bg-gray-300"
type="button"
onClick={onClickImport}
disabled={isImporting}
title="导入数据"
>
</button>
</div>

View File

@@ -28,9 +28,10 @@ export default function Setting() {
<button
type="button"
onClick={openModal}
className={`flex items-center justify-center rounded p-[2px] text-lg text-indigo-400 outline-none transition-colors duration-300 ease-in-out hover:bg-indigo-400 hover:text-white ${
isOpen && 'bg-indigo-400 text-white'
className={`flex items-center justify-center rounded p-[2px] text-lg text-indigo-500 outline-none transition-colors duration-300 ease-in-out hover:bg-indigo-400 hover:text-white ${
isOpen && 'bg-indigo-500 text-white'
}`}
title="打开设置对话框"
>
<Cog6ToothIcon className="icon" />
</button>
@@ -63,7 +64,7 @@ export default function Setting() {
<Dialog.Panel className="flex w-200 flex-col overflow-hidden rounded-2xl bg-white p-0 shadow-xl dark:bg-gray-800">
<div className="relative flex h-22 items-end justify-between rounded-t-lg border-b border-neutral-100 bg-stone-50 px-6 py-3 dark:border-neutral-700 dark:bg-gray-900">
<span className="text-3xl font-bold text-gray-600"></span>
<button type="button" onClick={() => setIsOpen(false)}>
<button type="button" onClick={() => setIsOpen(false)} title="关闭对话框">
<IconX className="absolute right-7 top-5 cursor-pointer text-gray-400" />
</button>
</div>

View File

@@ -119,7 +119,7 @@ export default function SharePicDialog({ showState, setShowState, randomChoose }
>
<Dialog.Panel className="relative transform overflow-hidden rounded-xl bg-white text-left shadow-xl transition-all dark:bg-gray-700">
<div className="flex flex-col items-center justify-center pb-10 pl-20 pr-14 pt-20">
<button className="absolute right-7 top-5" onClick={handleClose}>
<button className="absolute right-7 top-5" type="button" onClick={handleClose} title="关闭对话框">
<XMarkIcon className="h-6 w-6 text-gray-400" />
</button>
<div className="h-152 w-116">
@@ -143,7 +143,13 @@ export default function SharePicDialog({ showState, setShowState, randomChoose }
</div>
)}
</div>
<button onClick={handleDownload} ref={dialogFocusRef} className="btn-primary mr-9 mt-10 h-10">
<button
ref={dialogFocusRef}
className="btn-primary mr-9 mt-10 h-10"
type="button"
onClick={handleDownload}
title="保存"
>
</button>
</div>

View File

@@ -27,12 +27,14 @@ export default function SoundSwitcher() {
{({ open }) => (
<>
<Popover.Button
className={`flex items-center justify-center rounded p-[2px] text-lg text-indigo-400 outline-none transition-colors duration-300 ease-in-out hover:bg-indigo-400 hover:text-white ${
open ? 'bg-indigo-400 text-white' : ''
className={`flex items-center justify-center rounded p-[2px] text-lg text-indigo-500 outline-none transition-colors duration-300 ease-in-out hover:bg-indigo-400 hover:text-white ${
open ? 'bg-indigo-500 text-white' : ''
}`}
onFocus={(e) => {
e.target.blur()
}}
aria-label="音效设置"
title="音效设置"
>
<SpeakerWaveIcon className="icon" />
</Popover.Button>

View File

@@ -77,44 +77,52 @@ export default function Switcher() {
<Tooltip className="h-7 w-7" content="开关单个单词循环Ctrl + L">
<button
className={`p-[2px] ${state?.isLoopSingleWord ? 'text-indigo-400' : 'text-gray-400'} text-lg focus:outline-none`}
className={`p-[2px] ${state?.isLoopSingleWord ? 'text-indigo-500' : 'text-gray-500'} text-lg focus:outline-none`}
type="button"
onClick={(e) => {
changeLoopSingleWordState()
e.currentTarget.blur()
}}
aria-label="开关单个单词循环Ctrl + L"
>
{state?.isLoopSingleWord ? <IconRepeatOnce /> : <IconRepeatOff />}
</button>
</Tooltip>
<Tooltip className="h-7 w-7" content="开关英语显示Ctrl + V">
<button
className={`p-[2px] ${state?.isWordVisible ? 'text-indigo-400' : 'text-gray-400'} text-lg focus:outline-none`}
className={`p-[2px] ${state?.isWordVisible ? 'text-indigo-500' : 'text-gray-500'} text-lg focus:outline-none`}
type="button"
onClick={(e) => {
changeWordVisibleState()
e.currentTarget.blur()
}}
aria-label="开关英语显示Ctrl + V"
>
{state?.isWordVisible ? <EyeIcon className="icon" /> : <EyeSlashIcon className="icon" />}
</button>
</Tooltip>
<Tooltip className="h-7 w-7" content="开关释义显示Ctrl + T">
<button
className={`p-[2px] ${state?.isTransVisible ? 'text-indigo-400' : 'text-gray-400'} text-lg focus:outline-none`}
className={`p-[2px] ${state?.isTransVisible ? 'text-indigo-500' : 'text-gray-500'} text-lg focus:outline-none`}
type="button"
onClick={(e) => {
changeTransVisibleState()
e.currentTarget.blur()
}}
aria-label="开关释义显示Ctrl + T"
>
{state?.isTransVisible ? <IconLanguage /> : <IconLanguageOff />}
</button>
</Tooltip>
<Tooltip className="h-7 w-7" content="开关深色模式Ctrl + D">
<button
className={`p-[2px] text-lg text-indigo-400 focus:outline-none`}
className={`p-[2px] text-lg text-indigo-500 focus:outline-none`}
type="button"
onClick={(e) => {
changeDarkModeState()
e.currentTarget.blur()
}}
aria-label="开关深色模式Ctrl + D"
>
{isOpenDarkMode ? <MoonIcon className="icon" /> : <SunIcon className="icon" />}
</button>

View File

@@ -2,11 +2,11 @@ import VolumeHighIcon from './volume-icons/VolumeHieghIcon'
import VolumeIcon from './volume-icons/VolumeIcon'
import VolumeLowIcon from './volume-icons/VolumeLowIcon'
import VolumeMediumIcon from './volume-icons/VolumeMediumIcon'
import React, { useEffect, useState } from 'react'
import React, { MouseEventHandler, useEffect, useState } from 'react'
const volumeIcons = [VolumeIcon, VolumeLowIcon, VolumeMediumIcon, VolumeHighIcon]
export const SoundIcon = ({ duration = 500, animated = false, ...rest }: SoundIconProps) => {
export const SoundIcon = ({ duration = 500, animated = false, onClick, ...rest }: SoundIconProps) => {
const [animationFrameIndex, setAnimationFrameIndex] = useState(0)
useEffect(() => {
@@ -34,12 +34,17 @@ export const SoundIcon = ({ duration = 500, animated = false, ...rest }: SoundIc
const Icon = volumeIcons[animationFrameIndex]
return <Icon {...rest} />
return (
<button type="button" onClick={onClick}>
<Icon {...rest} />
</button>
)
}
export type SoundIconProps = {
animated?: boolean
duration?: number
onClick?: MouseEventHandler<HTMLButtonElement>
} & Omit<React.SVGProps<SVGSVGElement>, 'ref'>
export type SoundIconRef = {

View File

@@ -141,11 +141,13 @@ const App: React.FC = () => {
<Tooltip content="快捷键 Enter">
<button
className={`${
state.isTyping ? 'bg-gray-300 dark:bg-gray-700' : 'bg-indigo-400'
} btn-primary w-20 transition-colors duration-300`}
state.isTyping ? 'bg-gray-400 shadow-gray-200 dark:bg-gray-700' : 'bg-indigo-600 shadow-indigo-200'
} btn-primary w-20 shadow transition-colors duration-200`}
type="button"
onClick={onToggleIsTyping}
aria-label={state.isTyping ? '暂停' : '开始'}
>
{state.isTyping ? 'Pause' : 'Start'}
<span className="font-medium">{state.isTyping ? 'Pause' : 'Start'}</span>
</button>
</Tooltip>
<Tooltip content="跳过该词">
@@ -178,7 +180,7 @@ const App: React.FC = () => {
<Progress />
</>
) : (
<h3 className="animate-pulse select-none pb-4 text-xl text-gray-600 dark:text-gray-50"></h3>
<div className="animate-pulse select-none pb-4 text-xl text-gray-600 dark:text-gray-50"></div>
)}
</>
)