├── .eslintignore ├── .env ├── data ├── authors │ ├── default.mdx │ └── sparrowhawk.mdx ├── headerNavLinks.ts ├── references-data.bib ├── projectsData.ts ├── logo.svg ├── blog │ ├── xiangmuzhongzenmotianjiaeslintdaimajianchajieslintjibenpeizhi.md │ ├── hanshufangdou.md │ ├── CSS3shuxingbox-shadowdezhengqueshiyongzishi.md │ ├── jQueryshixianloading.md │ ├── jquery-barcode-JsBarcodetiaomashengchengchajian.md │ ├── shenkaobeiyuqiankaobei.md │ ├── vuexiangmuzhongyinruechart.jslaizhizuotubiao.md │ ├── CSS3shuxingborder-radiusdezhengqueshiyongzishi.md │ ├── xiugainpmjiyarndeyuan.md │ ├── npm-yarnchangyongmingling.md │ ├── benditiaoshiweixinjssdk.md │ ├── hanshujieliu.md │ ├── flexbujujiyingyong.md │ ├── zhengzebiaodashidexianhangduanyan(lookahead)hehouhangduanyan(lookbehind).md │ ├── liulanqineideshijianxunhuanjizhijimacrotaskyumicrotask.md │ ├── jiyuaxiosdexhrfengzhuang.md │ ├── shuzuquzhong-shuzuquzhongbingxunzhaozuidaxiang-shuzupaixu.md │ ├── guanchazhemoshiyufabudingyuemoshidequbie.md │ ├── yarn---frozen-lockfile-installshibai.md │ ├── jiluyiciphp-apache-mysql-composer-wampserveranzhuangguocheng.md │ ├── vuexiangmuzhongzenmoyinrumockjs.md │ ├── shiyongeslint+husky+lint-staged+prettiergoujiantongyidedaimafenggejidaimajianchagongzuoliu.md │ ├── jsneihuoquchicundeshuxingorfangfahuizong.md │ ├── inputyuansuxianzhishuruzhongwenneirongshichuxiandejieduanwenti.md │ ├── iframe-postMessage-kuayu-cookiecaozuo.md │ └── xiangmuzhongzenmopeizhiwebpack.md └── siteMetadata.js ├── example.jpg ├── public ├── baidu_verify_codeva-0YzCjssId1.html ├── googlef7877f1f0736e83e.html └── static │ ├── images │ ├── logo.png │ ├── logo2.png │ ├── logo3.png │ ├── logo4.png │ ├── logo5.png │ ├── logo6.png │ ├── avatar.png │ ├── google.png │ ├── ocean.jpeg │ ├── banner │ │ ├── css.png │ │ ├── js3.png │ │ ├── npm.png │ │ ├── ast.jpeg │ │ ├── git2.png │ │ ├── git3.png │ │ ├── glob.webp │ │ ├── js4.jpeg │ │ ├── react.png │ │ ├── vite.webp │ │ ├── vuejs.png │ │ ├── yarn.png │ │ ├── babel.jpeg │ │ ├── chrome.jpeg │ │ ├── chrome2.jpeg │ │ ├── docker1.png │ │ ├── eslint.png │ │ ├── husky.jpeg │ │ ├── husky.webp │ │ ├── lerna.jpeg │ │ ├── nodejs.png │ │ ├── nodejs2.png │ │ ├── rollup.jpeg │ │ ├── sentry.png │ │ ├── travis.png │ │ ├── weixin.jpeg │ │ ├── polyfill.jpeg │ │ ├── prettier.webp │ │ ├── vue-router.png │ │ ├── webpack3.jpeg │ │ ├── webpack5.jpeg │ │ ├── react-router.jpeg │ │ └── react_redux.png │ ├── canada │ │ ├── lake.jpg │ │ ├── maple.jpg │ │ ├── toronto.jpg │ │ └── mountains.jpg │ ├── time-machine.jpg │ ├── twitter-card.png │ ├── sparrowhawk-avatar.jpg │ └── yuque │ │ ├── 1649124812079-bf15a7aa-6c52-4228-9323-aedb670fd154.png │ │ ├── 1657156840059-055369e7-245f-4e29-945f-a3952b4c38fa.png │ │ ├── 1657156955589-3fdc4f3d-0a9b-4c95-8680-f299fcb55e81.png │ │ ├── 1657263749193-78ebd846-d168-4295-9060-faf19ae83da2.png │ │ ├── 1657263905011-e12583b1-c7c1-4241-aa68-863669ee546f.png │ │ ├── 1657263979755-c8b67cad-6a02-4f8d-8464-8c859e8fd8a3.png │ │ ├── 1657264072166-d660ae4f-787e-4e1c-9e22-8abdecb014a9.png │ │ ├── 1657265150252-b3d7148f-bc30-4d4c-b998-c33cdef729b7.png │ │ ├── 1657329453058-e05ccdee-9adb-4900-8a4a-e1424ed01e63.png │ │ ├── 1657329484617-2e376d0e-eb9a-4936-b3ea-58638fd92484.png │ │ ├── 1657329608454-c866536d-9645-473b-964f-7c8c31a68596.png │ │ ├── 1657330143864-fa97669a-0c6a-43c5-96f8-b7ba467cca2d.png │ │ ├── 1657332616699-9fddc8ac-af16-44b4-93bf-d206de2a6b41.png │ │ ├── 1657332816599-f702c008-1b9c-4e07-be0a-15a36897d92a.png │ │ ├── 1657333536305-affd8955-f9ef-49da-9007-9739049663a4.png │ │ ├── 1657333631232-f65a9a2b-654f-469d-945d-3a086b91454e.png │ │ ├── 1657333711975-72e8cf7e-ac8f-422a-9909-706d762f046c.png │ │ ├── 1657333734851-e828fb67-bfd4-4d04-93a0-a422a08ca5ad.png │ │ ├── 1657334050161-f0275a50-4eee-4ba8-a90f-d24d431b84b8.png │ │ ├── 1657334108331-4c0eff80-de17-40aa-accb-803890125765.png │ │ ├── 1657334133463-e420982c-b03f-4658-8769-7f766c8a2e8b.png │ │ ├── 1657334213805-a5d6e35c-b883-4f79-99e8-f2285b9f39f4.png │ │ ├── 1657334268324-231c6f0d-a84e-4b1f-8f4a-e0b7f8cf2cce.png │ │ ├── 1657334279827-152e132f-332c-4aa1-8d24-7493cd01a0cd.png │ │ ├── 1657334428358-063b9393-fa8c-486b-9737-666586adf21d.png │ │ ├── 1657334440640-71dc9596-bbd4-47dc-b631-eb32ab01b0b6.png │ │ ├── 1657334459348-7c300769-3cf0-4835-9995-abcd36c065ae.png │ │ ├── 1657334631420-e86408cb-052b-40fd-8bd2-83e605fde869.png │ │ ├── 1657334647615-32f64771-4093-4dd3-a35b-34f9ab831f81.png │ │ ├── 1657334665393-59e82069-bfbf-4101-a535-910651646631.png │ │ ├── 1657334863624-25f86f7d-0ed3-49ee-ac54-46c50a5379c5.png │ │ ├── 1657334879937-3762d0fc-3a86-4688-9e26-38cd90ad78d9.png │ │ ├── 1657334938466-c49f8bb6-6cfc-40fe-b015-925b5b50ea5a.png │ │ ├── 1657334952137-801bd408-7f37-427a-80a0-c58a2cc580e9.png │ │ ├── 1657334982605-b7058feb-77f3-4e1e-841d-1fe47302116a.png │ │ ├── 1657334998187-7e168359-8aac-440e-aba2-f160914e5254.png │ │ ├── 1659059089585-13a71bea-013a-4a47-8780-5111a62fc9ab.png │ │ ├── 1659059123772-a3d3e2e8-918e-4120-b798-29667bad4d1c.png │ │ ├── 1659061393865-0f9ffc08-a220-4c2d-84e0-532f83ab518f.png │ │ ├── 1659083788619-e8f50d44-0550-479d-a4e5-b593d695ccf0.png │ │ ├── 1659191662687-249045c8-b8b7-4381-a982-56dea96f86f0.png │ │ ├── 1659192347429-c8c07972-5e31-4f56-ba20-e1dd5f3b9902.png │ │ ├── 1659252870665-49f1497b-4ba9-4d32-b419-ccd640cc593a.png │ │ ├── 1659253111194-c91cbeac-9598-4a0f-8400-327a63d1bcf6.png │ │ ├── 1659254265224-e9042673-ee5a-4aad-91f9-04d3733c52cf.png │ │ ├── 1659255263778-e660558c-66cc-4f26-8974-a48849d70f06.png │ │ ├── 1662108760970-a067698e-c958-4517-916d-51bbaccacd5e.png │ │ ├── 1662108808647-2ea17cd3-9ce5-4e72-8553-b3d4ad0d3bcb.png │ │ ├── 1662108917769-84792cc5-d2cd-48a8-bcbc-fde0f1d5faac.png │ │ ├── 1662109199541-836a1b37-641a-4da3-a8f0-b0cd873e5685.png │ │ ├── 1662109240910-110c122e-9f91-4df8-88f1-7519f65f3903.png │ │ ├── 1662109284958-63c32ec9-504f-46ce-abba-9ab35ddbe053.png │ │ ├── 1662109376947-3f4fc8ea-64eb-46a3-b593-684b98b6564d.png │ │ ├── 1662169767927-11380ffd-3da6-4067-a603-5245d16da54a.png │ │ ├── 1667274524829-83f970d4-64e3-4a54-b196-c5f330f736bd.png │ │ ├── 1667282841907-c6a9c3dd-73f7-4ba0-9f58-a5fba598bf98.png │ │ ├── 1667283318076-51791e97-eb9a-4696-a0e3-28f69b402285.png │ │ ├── 1667283466712-d5bcb288-26fc-434a-aedc-486c10c945db.png │ │ ├── 1667284084790-42ac0fe1-e26e-4b81-b721-9379db280e39.png │ │ ├── 1667284786148-0324dc0b-9546-4b50-a186-f5491a755a11.png │ │ ├── 1667379577451-68fca078-1594-47ba-aee1-fb8063318b82.png │ │ ├── 1667379844086-3721e1a8-834c-452b-abfb-6269de55d95a.png │ │ ├── 1667379887362-6a86680f-0562-490a-b8dc-f61d3fad3d83.png │ │ ├── 1667379901195-b33b68f3-18e6-427b-a67e-2aa511310519.png │ │ └── 1667380166360-009f1c9b-0c30-4e0a-bd08-026fd2a2d9c7.png │ └── favicons │ ├── favicon.ico │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── mstile-150x150.png │ ├── apple-touch-icon.png │ ├── android-chrome-96x96.png │ ├── site.webmanifest │ └── safari-pinned-tab.svg ├── .husky ├── pre-commit └── _ │ └── husky.sh ├── postcss.config.js ├── components ├── Image.tsx ├── social-icons │ ├── mail.svg │ ├── facebook.svg │ ├── youtube.svg │ ├── twitter.svg │ ├── linkedin.svg │ ├── github.svg │ └── index.tsx ├── SectionContainer.tsx ├── PageTitle.tsx ├── Tag.tsx ├── toc │ ├── index.tsx │ └── AnchorLink.tsx ├── LayoutWrapper.tsx ├── MDXComponents.tsx ├── Link.tsx ├── GoogleAnalytics.tsx ├── Footer.tsx ├── Header.tsx ├── ThemeSwitch.tsx ├── Card.tsx ├── ScrollTopAndComment.tsx ├── MobileNav.tsx └── Article.tsx ├── prettier.config.js ├── css ├── banner.css ├── tailwind.css ├── SlugList.css ├── docsearch.css └── prism.css ├── next-env.d.ts ├── scripts ├── postbuild.mjs ├── rss.mjs ├── sitemap.mjs └── search.mjs ├── jsconfig.json ├── pages ├── api │ └── newsletter.ts ├── about.tsx ├── _app.tsx ├── projects.tsx ├── blog │ ├── index.tsx │ ├── page │ │ └── [page].tsx │ └── [...slug].tsx ├── 404.tsx ├── tags │ └── [tag].tsx ├── _document.tsx ├── tags.tsx └── index.tsx ├── .env.example ├── .gitignore ├── index.html ├── LICENSE ├── test.html ├── .eslintrc.js ├── tsconfig.json ├── tsconfig.recipe.json ├── layouts ├── AuthorLayout.tsx └── PostSimple.tsx ├── README.md ├── next.config.js ├── package.json └── contentlayer.config.ts /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | NEXT_PUBLIC_GOOGLE_ANALYTICS=G-GEQG7G7BMN -------------------------------------------------------------------------------- /data/authors/default.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | name: willson-wang 3 | --- 4 | 5 | -------------------------------------------------------------------------------- /example.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/example.jpg -------------------------------------------------------------------------------- /public/baidu_verify_codeva-0YzCjssId1.html: -------------------------------------------------------------------------------- 1 | 2efbdcfebf014043a2bb2515ac1ddae9 -------------------------------------------------------------------------------- /public/googlef7877f1f0736e83e.html: -------------------------------------------------------------------------------- 1 | google-site-verification: googlef7877f1f0736e83e.html -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx --no-install lint-staged 5 | -------------------------------------------------------------------------------- /public/static/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/logo.png -------------------------------------------------------------------------------- /public/static/images/logo2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/logo2.png -------------------------------------------------------------------------------- /public/static/images/logo3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/logo3.png -------------------------------------------------------------------------------- /public/static/images/logo4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/logo4.png -------------------------------------------------------------------------------- /public/static/images/logo5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/logo5.png -------------------------------------------------------------------------------- /public/static/images/logo6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/logo6.png -------------------------------------------------------------------------------- /public/static/images/avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/avatar.png -------------------------------------------------------------------------------- /public/static/images/google.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/google.png -------------------------------------------------------------------------------- /public/static/images/ocean.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/ocean.jpeg -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /public/static/favicons/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/favicons/favicon.ico -------------------------------------------------------------------------------- /public/static/images/banner/css.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/banner/css.png -------------------------------------------------------------------------------- /public/static/images/banner/js3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/banner/js3.png -------------------------------------------------------------------------------- /public/static/images/banner/npm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/banner/npm.png -------------------------------------------------------------------------------- /public/static/images/banner/ast.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/banner/ast.jpeg -------------------------------------------------------------------------------- /public/static/images/banner/git2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/banner/git2.png -------------------------------------------------------------------------------- /public/static/images/banner/git3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/banner/git3.png -------------------------------------------------------------------------------- /public/static/images/banner/glob.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/banner/glob.webp -------------------------------------------------------------------------------- /public/static/images/banner/js4.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/banner/js4.jpeg -------------------------------------------------------------------------------- /public/static/images/banner/react.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/banner/react.png -------------------------------------------------------------------------------- /public/static/images/banner/vite.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/banner/vite.webp -------------------------------------------------------------------------------- /public/static/images/banner/vuejs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/banner/vuejs.png -------------------------------------------------------------------------------- /public/static/images/banner/yarn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/banner/yarn.png -------------------------------------------------------------------------------- /public/static/images/canada/lake.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/canada/lake.jpg -------------------------------------------------------------------------------- /public/static/images/canada/maple.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/canada/maple.jpg -------------------------------------------------------------------------------- /public/static/images/time-machine.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/time-machine.jpg -------------------------------------------------------------------------------- /public/static/images/twitter-card.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/twitter-card.png -------------------------------------------------------------------------------- /public/static/favicons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/favicons/favicon-16x16.png -------------------------------------------------------------------------------- /public/static/favicons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/favicons/favicon-32x32.png -------------------------------------------------------------------------------- /public/static/images/banner/babel.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/banner/babel.jpeg -------------------------------------------------------------------------------- /public/static/images/banner/chrome.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/banner/chrome.jpeg -------------------------------------------------------------------------------- /public/static/images/banner/chrome2.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/banner/chrome2.jpeg -------------------------------------------------------------------------------- /public/static/images/banner/docker1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/banner/docker1.png -------------------------------------------------------------------------------- /public/static/images/banner/eslint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/banner/eslint.png -------------------------------------------------------------------------------- /public/static/images/banner/husky.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/banner/husky.jpeg -------------------------------------------------------------------------------- /public/static/images/banner/husky.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/banner/husky.webp -------------------------------------------------------------------------------- /public/static/images/banner/lerna.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/banner/lerna.jpeg -------------------------------------------------------------------------------- /public/static/images/banner/nodejs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/banner/nodejs.png -------------------------------------------------------------------------------- /public/static/images/banner/nodejs2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/banner/nodejs2.png -------------------------------------------------------------------------------- /public/static/images/banner/rollup.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/banner/rollup.jpeg -------------------------------------------------------------------------------- /public/static/images/banner/sentry.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/banner/sentry.png -------------------------------------------------------------------------------- /public/static/images/banner/travis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/banner/travis.png -------------------------------------------------------------------------------- /public/static/images/banner/weixin.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/banner/weixin.jpeg -------------------------------------------------------------------------------- /public/static/images/canada/toronto.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/canada/toronto.jpg -------------------------------------------------------------------------------- /public/static/favicons/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/favicons/mstile-150x150.png -------------------------------------------------------------------------------- /public/static/images/banner/polyfill.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/banner/polyfill.jpeg -------------------------------------------------------------------------------- /public/static/images/banner/prettier.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/banner/prettier.webp -------------------------------------------------------------------------------- /public/static/images/banner/vue-router.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/banner/vue-router.png -------------------------------------------------------------------------------- /public/static/images/banner/webpack3.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/banner/webpack3.jpeg -------------------------------------------------------------------------------- /public/static/images/banner/webpack5.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/banner/webpack5.jpeg -------------------------------------------------------------------------------- /public/static/images/canada/mountains.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/canada/mountains.jpg -------------------------------------------------------------------------------- /public/static/favicons/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/favicons/apple-touch-icon.png -------------------------------------------------------------------------------- /public/static/images/banner/react-router.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/banner/react-router.jpeg -------------------------------------------------------------------------------- /public/static/images/banner/react_redux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/banner/react_redux.png -------------------------------------------------------------------------------- /public/static/images/sparrowhawk-avatar.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/sparrowhawk-avatar.jpg -------------------------------------------------------------------------------- /public/static/favicons/android-chrome-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/favicons/android-chrome-96x96.png -------------------------------------------------------------------------------- /components/Image.tsx: -------------------------------------------------------------------------------- 1 | import NextImage, { ImageProps } from 'next/image' 2 | 3 | const Image = ({ ...rest }: ImageProps) => 4 | 5 | export default Image 6 | -------------------------------------------------------------------------------- /public/static/images/yuque/1649124812079-bf15a7aa-6c52-4228-9323-aedb670fd154.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/yuque/1649124812079-bf15a7aa-6c52-4228-9323-aedb670fd154.png -------------------------------------------------------------------------------- /public/static/images/yuque/1657156840059-055369e7-245f-4e29-945f-a3952b4c38fa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/yuque/1657156840059-055369e7-245f-4e29-945f-a3952b4c38fa.png -------------------------------------------------------------------------------- /public/static/images/yuque/1657156955589-3fdc4f3d-0a9b-4c95-8680-f299fcb55e81.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/yuque/1657156955589-3fdc4f3d-0a9b-4c95-8680-f299fcb55e81.png -------------------------------------------------------------------------------- /public/static/images/yuque/1657263749193-78ebd846-d168-4295-9060-faf19ae83da2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/yuque/1657263749193-78ebd846-d168-4295-9060-faf19ae83da2.png -------------------------------------------------------------------------------- /public/static/images/yuque/1657263905011-e12583b1-c7c1-4241-aa68-863669ee546f.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/yuque/1657263905011-e12583b1-c7c1-4241-aa68-863669ee546f.png -------------------------------------------------------------------------------- /public/static/images/yuque/1657263979755-c8b67cad-6a02-4f8d-8464-8c859e8fd8a3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/yuque/1657263979755-c8b67cad-6a02-4f8d-8464-8c859e8fd8a3.png -------------------------------------------------------------------------------- /public/static/images/yuque/1657264072166-d660ae4f-787e-4e1c-9e22-8abdecb014a9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/yuque/1657264072166-d660ae4f-787e-4e1c-9e22-8abdecb014a9.png -------------------------------------------------------------------------------- /public/static/images/yuque/1657265150252-b3d7148f-bc30-4d4c-b998-c33cdef729b7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/yuque/1657265150252-b3d7148f-bc30-4d4c-b998-c33cdef729b7.png -------------------------------------------------------------------------------- /public/static/images/yuque/1657329453058-e05ccdee-9adb-4900-8a4a-e1424ed01e63.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/yuque/1657329453058-e05ccdee-9adb-4900-8a4a-e1424ed01e63.png -------------------------------------------------------------------------------- /public/static/images/yuque/1657329484617-2e376d0e-eb9a-4936-b3ea-58638fd92484.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/yuque/1657329484617-2e376d0e-eb9a-4936-b3ea-58638fd92484.png -------------------------------------------------------------------------------- /public/static/images/yuque/1657329608454-c866536d-9645-473b-964f-7c8c31a68596.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/yuque/1657329608454-c866536d-9645-473b-964f-7c8c31a68596.png -------------------------------------------------------------------------------- /public/static/images/yuque/1657330143864-fa97669a-0c6a-43c5-96f8-b7ba467cca2d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/yuque/1657330143864-fa97669a-0c6a-43c5-96f8-b7ba467cca2d.png -------------------------------------------------------------------------------- /public/static/images/yuque/1657332616699-9fddc8ac-af16-44b4-93bf-d206de2a6b41.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/yuque/1657332616699-9fddc8ac-af16-44b4-93bf-d206de2a6b41.png -------------------------------------------------------------------------------- /public/static/images/yuque/1657332816599-f702c008-1b9c-4e07-be0a-15a36897d92a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/yuque/1657332816599-f702c008-1b9c-4e07-be0a-15a36897d92a.png -------------------------------------------------------------------------------- /public/static/images/yuque/1657333536305-affd8955-f9ef-49da-9007-9739049663a4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/yuque/1657333536305-affd8955-f9ef-49da-9007-9739049663a4.png -------------------------------------------------------------------------------- /public/static/images/yuque/1657333631232-f65a9a2b-654f-469d-945d-3a086b91454e.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/yuque/1657333631232-f65a9a2b-654f-469d-945d-3a086b91454e.png -------------------------------------------------------------------------------- /public/static/images/yuque/1657333711975-72e8cf7e-ac8f-422a-9909-706d762f046c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/yuque/1657333711975-72e8cf7e-ac8f-422a-9909-706d762f046c.png -------------------------------------------------------------------------------- /public/static/images/yuque/1657333734851-e828fb67-bfd4-4d04-93a0-a422a08ca5ad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/yuque/1657333734851-e828fb67-bfd4-4d04-93a0-a422a08ca5ad.png -------------------------------------------------------------------------------- /public/static/images/yuque/1657334050161-f0275a50-4eee-4ba8-a90f-d24d431b84b8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/yuque/1657334050161-f0275a50-4eee-4ba8-a90f-d24d431b84b8.png -------------------------------------------------------------------------------- /public/static/images/yuque/1657334108331-4c0eff80-de17-40aa-accb-803890125765.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/yuque/1657334108331-4c0eff80-de17-40aa-accb-803890125765.png -------------------------------------------------------------------------------- /public/static/images/yuque/1657334133463-e420982c-b03f-4658-8769-7f766c8a2e8b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/yuque/1657334133463-e420982c-b03f-4658-8769-7f766c8a2e8b.png -------------------------------------------------------------------------------- /public/static/images/yuque/1657334213805-a5d6e35c-b883-4f79-99e8-f2285b9f39f4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/yuque/1657334213805-a5d6e35c-b883-4f79-99e8-f2285b9f39f4.png -------------------------------------------------------------------------------- /public/static/images/yuque/1657334268324-231c6f0d-a84e-4b1f-8f4a-e0b7f8cf2cce.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/yuque/1657334268324-231c6f0d-a84e-4b1f-8f4a-e0b7f8cf2cce.png -------------------------------------------------------------------------------- /public/static/images/yuque/1657334279827-152e132f-332c-4aa1-8d24-7493cd01a0cd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/yuque/1657334279827-152e132f-332c-4aa1-8d24-7493cd01a0cd.png -------------------------------------------------------------------------------- /public/static/images/yuque/1657334428358-063b9393-fa8c-486b-9737-666586adf21d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/yuque/1657334428358-063b9393-fa8c-486b-9737-666586adf21d.png -------------------------------------------------------------------------------- /public/static/images/yuque/1657334440640-71dc9596-bbd4-47dc-b631-eb32ab01b0b6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/yuque/1657334440640-71dc9596-bbd4-47dc-b631-eb32ab01b0b6.png -------------------------------------------------------------------------------- /public/static/images/yuque/1657334459348-7c300769-3cf0-4835-9995-abcd36c065ae.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/yuque/1657334459348-7c300769-3cf0-4835-9995-abcd36c065ae.png -------------------------------------------------------------------------------- /public/static/images/yuque/1657334631420-e86408cb-052b-40fd-8bd2-83e605fde869.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/yuque/1657334631420-e86408cb-052b-40fd-8bd2-83e605fde869.png -------------------------------------------------------------------------------- /public/static/images/yuque/1657334647615-32f64771-4093-4dd3-a35b-34f9ab831f81.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/yuque/1657334647615-32f64771-4093-4dd3-a35b-34f9ab831f81.png -------------------------------------------------------------------------------- /public/static/images/yuque/1657334665393-59e82069-bfbf-4101-a535-910651646631.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/yuque/1657334665393-59e82069-bfbf-4101-a535-910651646631.png -------------------------------------------------------------------------------- /public/static/images/yuque/1657334863624-25f86f7d-0ed3-49ee-ac54-46c50a5379c5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/yuque/1657334863624-25f86f7d-0ed3-49ee-ac54-46c50a5379c5.png -------------------------------------------------------------------------------- /public/static/images/yuque/1657334879937-3762d0fc-3a86-4688-9e26-38cd90ad78d9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/yuque/1657334879937-3762d0fc-3a86-4688-9e26-38cd90ad78d9.png -------------------------------------------------------------------------------- /public/static/images/yuque/1657334938466-c49f8bb6-6cfc-40fe-b015-925b5b50ea5a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/yuque/1657334938466-c49f8bb6-6cfc-40fe-b015-925b5b50ea5a.png -------------------------------------------------------------------------------- /public/static/images/yuque/1657334952137-801bd408-7f37-427a-80a0-c58a2cc580e9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/yuque/1657334952137-801bd408-7f37-427a-80a0-c58a2cc580e9.png -------------------------------------------------------------------------------- /public/static/images/yuque/1657334982605-b7058feb-77f3-4e1e-841d-1fe47302116a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/yuque/1657334982605-b7058feb-77f3-4e1e-841d-1fe47302116a.png -------------------------------------------------------------------------------- /public/static/images/yuque/1657334998187-7e168359-8aac-440e-aba2-f160914e5254.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/yuque/1657334998187-7e168359-8aac-440e-aba2-f160914e5254.png -------------------------------------------------------------------------------- /public/static/images/yuque/1659059089585-13a71bea-013a-4a47-8780-5111a62fc9ab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/yuque/1659059089585-13a71bea-013a-4a47-8780-5111a62fc9ab.png -------------------------------------------------------------------------------- /public/static/images/yuque/1659059123772-a3d3e2e8-918e-4120-b798-29667bad4d1c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/yuque/1659059123772-a3d3e2e8-918e-4120-b798-29667bad4d1c.png -------------------------------------------------------------------------------- /public/static/images/yuque/1659061393865-0f9ffc08-a220-4c2d-84e0-532f83ab518f.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/yuque/1659061393865-0f9ffc08-a220-4c2d-84e0-532f83ab518f.png -------------------------------------------------------------------------------- /public/static/images/yuque/1659083788619-e8f50d44-0550-479d-a4e5-b593d695ccf0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/yuque/1659083788619-e8f50d44-0550-479d-a4e5-b593d695ccf0.png -------------------------------------------------------------------------------- /public/static/images/yuque/1659191662687-249045c8-b8b7-4381-a982-56dea96f86f0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/yuque/1659191662687-249045c8-b8b7-4381-a982-56dea96f86f0.png -------------------------------------------------------------------------------- /public/static/images/yuque/1659192347429-c8c07972-5e31-4f56-ba20-e1dd5f3b9902.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/yuque/1659192347429-c8c07972-5e31-4f56-ba20-e1dd5f3b9902.png -------------------------------------------------------------------------------- /public/static/images/yuque/1659252870665-49f1497b-4ba9-4d32-b419-ccd640cc593a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/yuque/1659252870665-49f1497b-4ba9-4d32-b419-ccd640cc593a.png -------------------------------------------------------------------------------- /public/static/images/yuque/1659253111194-c91cbeac-9598-4a0f-8400-327a63d1bcf6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/yuque/1659253111194-c91cbeac-9598-4a0f-8400-327a63d1bcf6.png -------------------------------------------------------------------------------- /public/static/images/yuque/1659254265224-e9042673-ee5a-4aad-91f9-04d3733c52cf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/yuque/1659254265224-e9042673-ee5a-4aad-91f9-04d3733c52cf.png -------------------------------------------------------------------------------- /public/static/images/yuque/1659255263778-e660558c-66cc-4f26-8974-a48849d70f06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/yuque/1659255263778-e660558c-66cc-4f26-8974-a48849d70f06.png -------------------------------------------------------------------------------- /public/static/images/yuque/1662108760970-a067698e-c958-4517-916d-51bbaccacd5e.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/yuque/1662108760970-a067698e-c958-4517-916d-51bbaccacd5e.png -------------------------------------------------------------------------------- /public/static/images/yuque/1662108808647-2ea17cd3-9ce5-4e72-8553-b3d4ad0d3bcb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/yuque/1662108808647-2ea17cd3-9ce5-4e72-8553-b3d4ad0d3bcb.png -------------------------------------------------------------------------------- /public/static/images/yuque/1662108917769-84792cc5-d2cd-48a8-bcbc-fde0f1d5faac.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/yuque/1662108917769-84792cc5-d2cd-48a8-bcbc-fde0f1d5faac.png -------------------------------------------------------------------------------- /public/static/images/yuque/1662109199541-836a1b37-641a-4da3-a8f0-b0cd873e5685.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/yuque/1662109199541-836a1b37-641a-4da3-a8f0-b0cd873e5685.png -------------------------------------------------------------------------------- /public/static/images/yuque/1662109240910-110c122e-9f91-4df8-88f1-7519f65f3903.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/yuque/1662109240910-110c122e-9f91-4df8-88f1-7519f65f3903.png -------------------------------------------------------------------------------- /public/static/images/yuque/1662109284958-63c32ec9-504f-46ce-abba-9ab35ddbe053.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/yuque/1662109284958-63c32ec9-504f-46ce-abba-9ab35ddbe053.png -------------------------------------------------------------------------------- /public/static/images/yuque/1662109376947-3f4fc8ea-64eb-46a3-b593-684b98b6564d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/yuque/1662109376947-3f4fc8ea-64eb-46a3-b593-684b98b6564d.png -------------------------------------------------------------------------------- /public/static/images/yuque/1662169767927-11380ffd-3da6-4067-a603-5245d16da54a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/yuque/1662169767927-11380ffd-3da6-4067-a603-5245d16da54a.png -------------------------------------------------------------------------------- /public/static/images/yuque/1667274524829-83f970d4-64e3-4a54-b196-c5f330f736bd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/yuque/1667274524829-83f970d4-64e3-4a54-b196-c5f330f736bd.png -------------------------------------------------------------------------------- /public/static/images/yuque/1667282841907-c6a9c3dd-73f7-4ba0-9f58-a5fba598bf98.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/yuque/1667282841907-c6a9c3dd-73f7-4ba0-9f58-a5fba598bf98.png -------------------------------------------------------------------------------- /public/static/images/yuque/1667283318076-51791e97-eb9a-4696-a0e3-28f69b402285.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/yuque/1667283318076-51791e97-eb9a-4696-a0e3-28f69b402285.png -------------------------------------------------------------------------------- /public/static/images/yuque/1667283466712-d5bcb288-26fc-434a-aedc-486c10c945db.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/yuque/1667283466712-d5bcb288-26fc-434a-aedc-486c10c945db.png -------------------------------------------------------------------------------- /public/static/images/yuque/1667284084790-42ac0fe1-e26e-4b81-b721-9379db280e39.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/yuque/1667284084790-42ac0fe1-e26e-4b81-b721-9379db280e39.png -------------------------------------------------------------------------------- /public/static/images/yuque/1667284786148-0324dc0b-9546-4b50-a186-f5491a755a11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/yuque/1667284786148-0324dc0b-9546-4b50-a186-f5491a755a11.png -------------------------------------------------------------------------------- /public/static/images/yuque/1667379577451-68fca078-1594-47ba-aee1-fb8063318b82.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/yuque/1667379577451-68fca078-1594-47ba-aee1-fb8063318b82.png -------------------------------------------------------------------------------- /public/static/images/yuque/1667379844086-3721e1a8-834c-452b-abfb-6269de55d95a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/yuque/1667379844086-3721e1a8-834c-452b-abfb-6269de55d95a.png -------------------------------------------------------------------------------- /public/static/images/yuque/1667379887362-6a86680f-0562-490a-b8dc-f61d3fad3d83.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/yuque/1667379887362-6a86680f-0562-490a-b8dc-f61d3fad3d83.png -------------------------------------------------------------------------------- /public/static/images/yuque/1667379901195-b33b68f3-18e6-427b-a67e-2aa511310519.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/yuque/1667379901195-b33b68f3-18e6-427b-a67e-2aa511310519.png -------------------------------------------------------------------------------- /public/static/images/yuque/1667380166360-009f1c9b-0c30-4e0a-bd08-026fd2a2d9c7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willson-wang/Blog/HEAD/public/static/images/yuque/1667380166360-009f1c9b-0c30-4e0a-bd08-026fd2a2d9c7.png -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | semi: false, 3 | singleQuote: true, 4 | printWidth: 100, 5 | tabWidth: 2, 6 | useTabs: false, 7 | trailingComma: 'es5', 8 | bracketSpacing: true, 9 | } 10 | -------------------------------------------------------------------------------- /css/banner.css: -------------------------------------------------------------------------------- 1 | .anim { 2 | margin-bottom: 30px; 3 | } 4 | 5 | .anim, 6 | .anim svg { 7 | position: relative; 8 | width: 100%; 9 | height: 420px; 10 | } 11 | 12 | .anim path { 13 | stroke-width: 2; 14 | } -------------------------------------------------------------------------------- /next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /scripts/postbuild.mjs: -------------------------------------------------------------------------------- 1 | import rss from './rss.mjs' 2 | import sitemap from './sitemap.mjs' 3 | import search from './search.mjs' 4 | 5 | async function postbuild() { 6 | await Promise.all([rss(), sitemap(), search()]) 7 | } 8 | 9 | postbuild() 10 | -------------------------------------------------------------------------------- /components/social-icons/mail.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /data/headerNavLinks.ts: -------------------------------------------------------------------------------- 1 | const headerNavLinks = [ 2 | { href: '/blog', title: '文章' }, 3 | { href: '/tags', title: '标签' }, 4 | // { href: '/projects', title: 'Projects' }, 5 | // { href: '/about', title: 'About' }, 6 | ] 7 | 8 | export default headerNavLinks 9 | -------------------------------------------------------------------------------- /components/SectionContainer.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode } from 'react' 2 | 3 | interface Props { 4 | children: ReactNode 5 | } 6 | 7 | export default function SectionContainer({ children }: Props) { 8 | return ( 9 |
{children}
10 | ) 11 | } 12 | -------------------------------------------------------------------------------- /scripts/rss.mjs: -------------------------------------------------------------------------------- 1 | import { generateRSS } from 'pliny/utils/generate-rss.js' 2 | import siteMetadata from '../data/siteMetadata.js' 3 | import { allBlogs } from '../.contentlayer/generated/index.mjs' 4 | 5 | const rss = () => { 6 | generateRSS(siteMetadata, allBlogs) 7 | console.log('RSS feed generated...') 8 | } 9 | export default rss 10 | -------------------------------------------------------------------------------- /public/static/favicons/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "", 3 | "short_name": "", 4 | "icons": [ 5 | { 6 | "src": "/android-chrome-96x96.png", 7 | "sizes": "96x96", 8 | "type": "image/png" 9 | } 10 | ], 11 | "theme_color": "#000000", 12 | "background_color": "#000000", 13 | "display": "standalone" 14 | } 15 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "paths": { 5 | "@/components/*": ["components/*"], 6 | "@/data/*": ["data/*"], 7 | "@/layouts/*": ["layouts/*"], 8 | "@/lib/*": ["lib/*"], 9 | "@/css/*": ["css/*"], 10 | "contentlayer/generated": ["./.contentlayer/generated"] 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /scripts/sitemap.mjs: -------------------------------------------------------------------------------- 1 | import { generateSitemap } from 'pliny/utils/generate-sitemap.js' 2 | import siteMetadata from '../data/siteMetadata.js' 3 | import { allBlogs } from '../.contentlayer/generated/index.mjs' 4 | 5 | const sitemap = () => { 6 | generateSitemap(siteMetadata.siteUrl, allBlogs) 7 | console.log('Sitemap generated...') 8 | } 9 | export default sitemap 10 | -------------------------------------------------------------------------------- /components/PageTitle.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode } from 'react' 2 | 3 | interface Props { 4 | children: ReactNode 5 | } 6 | 7 | export default function PageTitle({ children }: Props) { 8 | return ( 9 |

10 | {children} 11 |

12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /components/social-icons/facebook.svg: -------------------------------------------------------------------------------- 1 | Facebook icon -------------------------------------------------------------------------------- /pages/api/newsletter.ts: -------------------------------------------------------------------------------- 1 | import { NewsletterAPI } from 'pliny/newsletter' 2 | import siteMetadata from '@/data/siteMetadata' 3 | 4 | export default NewsletterAPI({ 5 | provider: siteMetadata.newsletter.provider, 6 | }) 7 | 8 | // export default async function auth(req: NextApiRequest, res: NextApiResponse) { 9 | // // Do whatever you want here, before the request is passed down 10 | // return await NewsletterAPI(req, res, { 11 | // provider: ... 12 | // }) 13 | // } 14 | -------------------------------------------------------------------------------- /components/social-icons/youtube.svg: -------------------------------------------------------------------------------- 1 | YouTube icon -------------------------------------------------------------------------------- /components/Tag.tsx: -------------------------------------------------------------------------------- 1 | import Link from 'next/link' 2 | import { kebabCase } from 'pliny/utils/kebabCase' 3 | 4 | interface Props { 5 | text: string 6 | } 7 | 8 | const Tag = ({ text }: Props) => { 9 | return ( 10 | 14 | {text.split(' ').join('-')} 15 | 16 | ) 17 | } 18 | 19 | export default Tag 20 | -------------------------------------------------------------------------------- /scripts/search.mjs: -------------------------------------------------------------------------------- 1 | import { writeFileSync } from 'fs' 2 | import { allCoreContent } from 'pliny/utils/contentlayer.js' 3 | import { allBlogs } from '../.contentlayer/generated/index.mjs' 4 | import siteMetadata from '../data/siteMetadata.js' 5 | 6 | const search = () => { 7 | if (siteMetadata?.search?.kbarConfig?.searchDocumentsPath) { 8 | writeFileSync( 9 | `public/${siteMetadata.search.kbarConfig.searchDocumentsPath}`, 10 | JSON.stringify(allCoreContent(allBlogs)) 11 | ) 12 | console.log('Local search index generated...') 13 | } 14 | } 15 | export default search 16 | -------------------------------------------------------------------------------- /components/social-icons/twitter.svg: -------------------------------------------------------------------------------- 1 | Twitter icon -------------------------------------------------------------------------------- /components/social-icons/linkedin.svg: -------------------------------------------------------------------------------- 1 | LinkedIn icon -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # visit https://giscus.app to get your Giscus ids 2 | NEXT_PUBLIC_GISCUS_REPO= 3 | NEXT_PUBLIC_GISCUS_REPOSITORY_ID= 4 | NEXT_PUBLIC_GISCUS_CATEGORY= 5 | NEXT_PUBLIC_GISCUS_CATEGORY_ID= 6 | NEXT_PUBLIC_UTTERANCES_REPO= 7 | NEXT_PUBLIC_DISQUS_SHORTNAME= 8 | 9 | 10 | MAILCHIMP_API_KEY= 11 | MAILCHIMP_API_SERVER= 12 | MAILCHIMP_AUDIENCE_ID= 13 | 14 | BUTTONDOWN_API_KEY= 15 | 16 | CONVERTKIT_API_KEY= 17 | # curl https://api.convertkit.com/v3/forms?api_key= to get your form ID 18 | CONVERTKIT_FORM_ID= 19 | 20 | KLAVIYO_API_KEY= 21 | KLAVIYO_LIST_ID= 22 | 23 | REVUE_API_KEY= 24 | 25 | EMAILOCTOPUS_API_KEY= 26 | EMAILOCTOPUS_LIST_ID= 27 | -------------------------------------------------------------------------------- /.husky/_/husky.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | if [ -z "$husky_skip_init" ]; then 3 | debug () { 4 | [ "$HUSKY_DEBUG" = "1" ] && echo "husky (debug) - $1" 5 | } 6 | 7 | readonly hook_name="$(basename "$0")" 8 | debug "starting $hook_name..." 9 | 10 | if [ "$HUSKY" = "0" ]; then 11 | debug "HUSKY env variable is set to 0, skipping hook" 12 | exit 0 13 | fi 14 | 15 | if [ -f ~/.huskyrc ]; then 16 | debug "sourcing ~/.huskyrc" 17 | . ~/.huskyrc 18 | fi 19 | 20 | export readonly husky_skip_init=1 21 | sh -e "$0" "$@" 22 | exitCode="$?" 23 | 24 | if [ $exitCode != 0 ]; then 25 | echo "husky - $hook_name hook exited with code $exitCode (error)" 26 | exit $exitCode 27 | fi 28 | 29 | exit 0 30 | fi 31 | -------------------------------------------------------------------------------- /components/toc/index.tsx: -------------------------------------------------------------------------------- 1 | import type { FC } from 'react' 2 | import React from 'react' 3 | import AnchorLink from './AnchorLink' 4 | 5 | const SlugsList: FC<{ slugs: any; className?: string }> = ({ slugs, ...props }) => { 6 | return ( 7 |
    8 | {slugs 9 | .filter(({ depth }: { depth: number }) => depth >= 1 && depth < 4) 10 | .map((slug: any) => ( 11 |
  • 12 | 13 | {slug.value} 14 | 15 |
  • 16 | ))} 17 |
18 | ) 19 | } 20 | 21 | export default SlugsList 22 | -------------------------------------------------------------------------------- /components/LayoutWrapper.tsx: -------------------------------------------------------------------------------- 1 | import { Inter } from '@next/font/google' 2 | import SectionContainer from './SectionContainer' 3 | import Footer from './Footer' 4 | import { ReactNode } from 'react' 5 | import Header from './Header' 6 | 7 | interface Props { 8 | children: ReactNode 9 | } 10 | 11 | const inter = Inter({ 12 | subsets: ['latin'], 13 | }) 14 | 15 | const LayoutWrapper = ({ children }: Props) => { 16 | return ( 17 | 18 |
19 |
20 |
{children}
21 |
22 |
23 |
24 | ) 25 | } 26 | 27 | export default LayoutWrapper 28 | -------------------------------------------------------------------------------- /css/tailwind.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | .task-list-item::before { 6 | @apply hidden; 7 | } 8 | 9 | .task-list-item { 10 | @apply list-none; 11 | } 12 | 13 | .footnotes { 14 | @apply mt-12 border-t border-gray-200 pt-8 dark:border-gray-700; 15 | } 16 | 17 | .data-footnote-backref { 18 | @apply no-underline; 19 | } 20 | 21 | .csl-entry { 22 | @apply my-5; 23 | } 24 | 25 | /* https://stackoverflow.com/questions/61083813/how-to-avoid-internal-autofill-selected-style-to-be-applied */ 26 | input:-webkit-autofill, 27 | input:-webkit-autofill:focus { 28 | transition: background-color 600000s 0s, color 600000s 0s; 29 | } 30 | 31 | .prose > p:last-of-type a[target="_blank"] { 32 | display: block; 33 | } 34 | -------------------------------------------------------------------------------- /components/MDXComponents.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/display-name */ 2 | import React from 'react' 3 | import { MDXLayout, ComponentMap } from 'pliny/mdx-components' 4 | import { TOCInline } from 'pliny/ui/TOCInline' 5 | import { Pre } from 'pliny/ui/Pre' 6 | import { BlogNewsletterForm } from 'pliny/ui/NewsletterForm' 7 | 8 | import Image from './Image' 9 | import CustomLink from './Link' 10 | 11 | export const Wrapper = ({ layout, content, ...rest }: MDXLayout) => { 12 | const Layout = require(`../layouts/${layout}`).default 13 | return 14 | } 15 | 16 | export const MDXComponents: ComponentMap = { 17 | Image, 18 | TOCInline, 19 | a: CustomLink, 20 | pre: Pre, 21 | wrapper: Wrapper, 22 | BlogNewsletterForm, 23 | } 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | /.yarn/* 8 | !/.yarn/releases 9 | !/.yarn/plugins 10 | !/.yarn/sdks 11 | 12 | # testing 13 | /coverage 14 | 15 | # next.js 16 | /.next/ 17 | /out/ 18 | public/sitemap.xml 19 | .vercel 20 | 21 | # production 22 | /build 23 | *.xml 24 | 25 | # rss feed 26 | /public/feed.xml 27 | 28 | # search 29 | /public/search.json 30 | 31 | # misc 32 | .DS_Store 33 | 34 | # debug 35 | *.log 36 | npm-debug.log* 37 | yarn-debug.log* 38 | yarn-error.log* 39 | 40 | # local env files 41 | .env.local 42 | .env.development.local 43 | .env.test.local 44 | .env.production.local 45 | 46 | # Contentlayer 47 | .contentlayer 48 | 49 | 50 | -------------------------------------------------------------------------------- /pages/about.tsx: -------------------------------------------------------------------------------- 1 | // import { MDXLayoutRenderer } from '@/components/MDXComponents' 2 | import { InferGetStaticPropsType } from 'next' 3 | import { allAuthors } from 'contentlayer/generated' 4 | import { MDXLayoutRenderer } from 'pliny/mdx-components' 5 | import { MDXComponents } from '@/components/MDXComponents' 6 | 7 | const DEFAULT_LAYOUT = 'AuthorLayout' 8 | 9 | export const getStaticProps = async () => { 10 | const author = allAuthors.find((p) => p.slug === 'default') 11 | return { props: { author } } 12 | } 13 | 14 | export default function About({ author }: InferGetStaticPropsType) { 15 | return ( 16 | 21 | ) 22 | } 23 | -------------------------------------------------------------------------------- /components/social-icons/github.svg: -------------------------------------------------------------------------------- 1 | GitHub icon -------------------------------------------------------------------------------- /components/Link.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable jsx-a11y/anchor-has-content */ 2 | import Link from 'next/link' 3 | import { AnchorHTMLAttributes, DetailedHTMLProps } from 'react' 4 | import AnchorLink from './toc/AnchorLink' 5 | 6 | const CustomLink = ({ 7 | href, 8 | ...rest 9 | }: DetailedHTMLProps, HTMLAnchorElement>) => { 10 | const isInternalLink = href && href.startsWith('/') 11 | const isAnchorLink = href && href.startsWith('#') 12 | 13 | if (isInternalLink) { 14 | // @ts-ignore 15 | return 16 | } 17 | 18 | if (isAnchorLink) { 19 | // return 20 | return 21 | } 22 | 23 | return 24 | } 25 | 26 | export default CustomLink 27 | -------------------------------------------------------------------------------- /data/references-data.bib: -------------------------------------------------------------------------------- 1 | @article{Nash1950, 2 | title={Equilibrium points in n-person games}, 3 | author={Nash, John}, 4 | journal={Proceedings of the national academy of sciences}, 5 | volume={36}, 6 | number={1}, 7 | pages={48--49}, 8 | year={1950}, 9 | publisher={USA} 10 | } 11 | 12 | @article{Nash1951, 13 | title={Non-cooperative games}, 14 | author={Nash, John}, 15 | journal={Annals of mathematics}, 16 | pages={286--295}, 17 | year={1951}, 18 | publisher={JSTOR} 19 | } 20 | 21 | @Manual{Macfarlane2006, 22 | url={https://pandoc.org/}, 23 | title={Pandoc: a universal document converter}, 24 | author={MacFarlane, John}, 25 | year={2006} 26 | } 27 | 28 | @book{Xie2016, 29 | title={Bookdown: authoring books and technical documents with R markdown}, 30 | author={Xie, Yihui}, 31 | year={2016}, 32 | publisher={CRC Press} 33 | } -------------------------------------------------------------------------------- /data/projectsData.ts: -------------------------------------------------------------------------------- 1 | const projectsData = [ 2 | // { 3 | // title: 'A Search Engine', 4 | // description: `What if you could look up any information in the world? Webpages, images, videos 5 | // and more. Google has many features to help you find exactly what you're looking 6 | // for.`, 7 | // imgSrc: '/static/images/google.png', 8 | // href: 'https://www.google.com', 9 | // }, 10 | // { 11 | // title: 'The Time Machine', 12 | // description: `Imagine being able to travel back in time or to the future. Simple turn the knob 13 | // to the desired date and press "Go". No more worrying about lost keys or 14 | // forgotten headphones with this simple yet affordable solution.`, 15 | // imgSrc: '/static/images/time-machine.jpg', 16 | // href: '/blog/the-time-machine', 17 | // }, 18 | ] 19 | 20 | export default projectsData 21 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 26 | 27 | 28 |

Other content.

29 |
aaaa
30 | 31 | 32 | -------------------------------------------------------------------------------- /data/authors/sparrowhawk.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | name: Sparrow Hawk 3 | avatar: /static/images/sparrowhawk-avatar.jpg 4 | occupation: Wizard of Earthsea 5 | company: Earthsea 6 | twitter: https://twitter.com/sparrowhawk 7 | linkedin: https://www.linkedin.com/sparrowhawk 8 | --- 9 | 10 | At birth, Ged was given the child-name Duny by his mother. He was born on the island of Gont, as a son of a bronzesmith. His mother died before he reached the age of one. As a small boy, Ged had overheard the village witch, his maternal aunt, using various words of power to call goats. Ged later used the words without an understanding of their meanings, to surprising effect. 11 | 12 | The witch knew that using words of power effectively without understanding them required innate power, so she endeavored to teach him what little she knew. After learning more from her, he was able to call animals to him. Particularly, he was seen in the company of wild sparrowhawks so often that his "use name" became Sparrowhawk. 13 | -------------------------------------------------------------------------------- /components/GoogleAnalytics.tsx: -------------------------------------------------------------------------------- 1 | import Script from 'next/script' 2 | 3 | import siteMetadata from '@/data/siteMetadata' 4 | 5 | const GAScript = () => { 6 | return ( 7 | <> 8 | 23 | 24 | ) 25 | } 26 | 27 | export default GAScript 28 | 29 | // https://developers.google.com/analytics/devguides/collection/gtagjs/events 30 | export const logEvent = (action, category, label, value) => { 31 | window.gtag?.('event', action, { 32 | event_category: category, 33 | event_label: label, 34 | value: value, 35 | }) 36 | } 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 willson-wang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 34 | 35 | 36 |

Other content.

37 |
aaaa
38 |
bbbb
39 |
40 | 41 | 42 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: '@typescript-eslint/parser', 4 | env: { 5 | browser: true, 6 | amd: true, 7 | node: true, 8 | es6: true, 9 | }, 10 | plugins: ['@typescript-eslint'], 11 | extends: [ 12 | 'eslint:recommended', 13 | 'plugin:@typescript-eslint/eslint-recommended', 14 | 'plugin:@typescript-eslint/recommended', 15 | 'plugin:jsx-a11y/recommended', 16 | 'plugin:prettier/recommended', 17 | 'next', 18 | 'next/core-web-vitals', 19 | ], 20 | rules: { 21 | 'prettier/prettier': 'error', 22 | 'react/react-in-jsx-scope': 'off', 23 | 'jsx-a11y/anchor-is-valid': [ 24 | 'error', 25 | { 26 | components: ['Link'], 27 | specialLink: ['hrefLeft', 'hrefRight'], 28 | aspects: ['invalidHref', 'preferButton'], 29 | }, 30 | ], 31 | "jsx-a11y/aria-role": 0, 32 | 'react/prop-types': 0, 33 | 'no-unused-vars': 0, 34 | 'react/no-unescaped-entities': 0, 35 | '@typescript-eslint/explicit-module-boundary-types': 'off', 36 | '@typescript-eslint/no-var-requires': 'off', 37 | '@typescript-eslint/ban-ts-comment': 'off', 38 | }, 39 | } 40 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "incremental": true, 4 | "target": "ES6", 5 | "lib": ["dom", "dom.iterable", "esnext"], 6 | "allowJs": true, 7 | "skipLibCheck": true, 8 | "strict": false, 9 | "forceConsistentCasingInFileNames": true, 10 | "noEmit": true, 11 | "composite": true, 12 | "esModuleInterop": true, 13 | "module": "esnext", 14 | "moduleResolution": "node", 15 | "resolveJsonModule": true, 16 | "isolatedModules": true, 17 | "jsx": "preserve", 18 | "baseUrl": ".", 19 | "paths": { 20 | "@/components/*": ["components/*"], 21 | "@/data/*": ["data/*"], 22 | "@/layouts/*": ["layouts/*"], 23 | "@/lib/*": ["lib/*"], 24 | "@/css/*": ["css/*"], 25 | "contentlayer/generated": ["./.contentlayer/generated"] 26 | }, 27 | "plugins": [ 28 | { 29 | "name": "next" 30 | } 31 | ] 32 | }, 33 | "include": [ 34 | "next-env.d.ts", 35 | "**/*.js", 36 | "**/*.ts", 37 | "**/*.tsx", 38 | ".contentlayer/generated", 39 | ".contentlayer/generated/**/*.json", 40 | ".next/types/**/*.ts" 41 | ], 42 | "exclude": ["node_modules"] 43 | } 44 | -------------------------------------------------------------------------------- /tsconfig.recipe.json: -------------------------------------------------------------------------------- 1 | // Needed to resolve path reference to type check recipes 2 | // Can be deleted if you are cloning the app 3 | { 4 | "compilerOptions": { 5 | "incremental": true, 6 | "target": "ES6", 7 | "lib": ["dom", "dom.iterable", "esnext"], 8 | "allowJs": true, 9 | "skipLibCheck": true, 10 | "strict": false, 11 | "forceConsistentCasingInFileNames": true, 12 | "emitDeclarationOnly": true, 13 | "composite": true, 14 | "esModuleInterop": true, 15 | "module": "esnext", 16 | "moduleResolution": "node", 17 | "resolveJsonModule": true, 18 | "isolatedModules": true, 19 | "jsx": "preserve", 20 | "baseUrl": ".", 21 | "paths": { 22 | "@/components/*": ["components/*"], 23 | "@/data/*": ["data/*"], 24 | "@/layouts/*": ["layouts/*"], 25 | "@/lib/*": ["lib/*"], 26 | "@/css/*": ["css/*"], 27 | "contentlayer/generated": ["./.contentlayer/generated"] 28 | } 29 | }, 30 | "include": [ 31 | "next-env.d.ts", 32 | "**/*.js", 33 | "**/*.ts", 34 | "**/*.tsx", 35 | ".contentlayer/generated", 36 | ".contentlayer/generated/**/*.json" 37 | ], 38 | "exclude": ["node_modules"] 39 | } 40 | -------------------------------------------------------------------------------- /components/social-icons/index.tsx: -------------------------------------------------------------------------------- 1 | import Mail from './mail.svg' 2 | import Github from './github.svg' 3 | import Facebook from './facebook.svg' 4 | import Youtube from './youtube.svg' 5 | import Linkedin from './linkedin.svg' 6 | import Twitter from './twitter.svg' 7 | 8 | // Icons taken from: https://simpleicons.org/ 9 | 10 | const components = { 11 | mail: Mail, 12 | github: Github, 13 | facebook: Facebook, 14 | youtube: Youtube, 15 | linkedin: Linkedin, 16 | twitter: Twitter, 17 | } 18 | 19 | const SocialIcon = ({ kind, href, size = 8 }) => { 20 | if (!href || (kind === 'mail' && !/^mailto:\w+([.-]?\w+)@\w+([.-]?\w+)(.\w{2,3})+$/.test(href))) 21 | return null 22 | 23 | const SocialSvg = components[kind] 24 | 25 | return ( 26 |
32 | {kind} 33 | 36 | 37 | ) 38 | } 39 | 40 | export default SocialIcon 41 | -------------------------------------------------------------------------------- /public/static/favicons/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | Created by potrace 1.11, written by Peter Selinger 2001-2013 9 | 10 | 12 | 16 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import '@/css/tailwind.css' 2 | import '@/css/prism.css' 3 | import 'katex/dist/katex.css' 4 | import '@/css/SlugList.css' 5 | import '@/css/banner.css' 6 | // import '@/css/docsearch.css' // Uncomment if using algolia docsearch 7 | // import '@docsearch/css' // Uncomment if using algolia docsearch 8 | 9 | import { ThemeProvider } from 'next-themes' 10 | import type { AppProps } from 'next/app' 11 | import Head from 'next/head' 12 | 13 | import siteMetadata from '@/data/siteMetadata' 14 | import { Analytics } from 'pliny/analytics' 15 | import { SearchProvider } from 'pliny/search' 16 | import LayoutWrapper from '@/components/LayoutWrapper' 17 | 18 | export default function App({ Component, pageProps }: AppProps) { 19 | return ( 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | ) 32 | } 33 | -------------------------------------------------------------------------------- /components/Footer.tsx: -------------------------------------------------------------------------------- 1 | import Link from './Link' 2 | import siteMetadata from '@/data/siteMetadata' 3 | import SocialIcon from '@/components/social-icons' 4 | 5 | export default function Footer() { 6 | return ( 7 |
8 |
9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 |
18 |
{siteMetadata.author}
19 |
{` • `}
20 |
{`© ${new Date().getFullYear()}`}
21 |
{` • `}
22 | {siteMetadata.title} 23 |
24 |
25 |
26 | ) 27 | } 28 | -------------------------------------------------------------------------------- /data/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /pages/projects.tsx: -------------------------------------------------------------------------------- 1 | import siteMetadata from '@/data/siteMetadata' 2 | import projectsData from '@/data/projectsData' 3 | import Card from '@/components/Card' 4 | import { PageSEO } from '@/components/SEO' 5 | 6 | export default function Projects() { 7 | return ( 8 | <> 9 | 10 |
11 |
12 |

13 | Projects 14 |

15 |

16 | Showcase your projects with a hero image (16 x 9) 17 |

18 |
19 |
20 |
21 | {projectsData.map((d) => ( 22 | 29 | ))} 30 |
31 |
32 |
33 | 34 | ) 35 | } 36 | -------------------------------------------------------------------------------- /pages/blog/index.tsx: -------------------------------------------------------------------------------- 1 | import siteMetadata from '@/data/siteMetadata' 2 | import ListLayout from '@/layouts/ListLayout' 3 | import { PageSEO } from '@/components/SEO' 4 | import { sortedBlogPost, allCoreContent } from 'pliny/utils/contentlayer' 5 | import { InferGetStaticPropsType } from 'next' 6 | import { allBlogs } from 'contentlayer/generated' 7 | import type { Blog } from 'contentlayer/generated' 8 | 9 | export const POSTS_PER_PAGE = 10 10 | 11 | export const getStaticProps = async () => { 12 | const posts = sortedBlogPost(allBlogs) as Blog[] 13 | const initialDisplayPosts = posts.slice(0, POSTS_PER_PAGE) 14 | const pagination = { 15 | currentPage: 1, 16 | totalPages: Math.ceil(posts.length / POSTS_PER_PAGE), 17 | } 18 | 19 | return { 20 | props: { 21 | initialDisplayPosts: allCoreContent(initialDisplayPosts), 22 | posts: allCoreContent(posts), 23 | pagination, 24 | }, 25 | } 26 | } 27 | 28 | export default function BlogPage({ 29 | posts, 30 | initialDisplayPosts, 31 | pagination, 32 | }: InferGetStaticPropsType) { 33 | return ( 34 | <> 35 | 36 | 42 | 43 | ) 44 | } 45 | -------------------------------------------------------------------------------- /pages/404.tsx: -------------------------------------------------------------------------------- 1 | import Link from '@/components/Link' 2 | import { PageSEO } from '@/components/SEO' 3 | 4 | export default function FourZeroFour() { 5 | return ( 6 | <> 7 | 8 |
9 |
10 |

11 | 404 12 |

13 |
14 |
15 |

16 | Sorry we couldn't find this page. 17 |

18 |

19 | But dont worry, you can find plenty of other things on our homepage. 20 |

21 | 25 | Back to homepage 26 | 27 |
28 |
29 | 30 | ) 31 | } 32 | -------------------------------------------------------------------------------- /data/blog/xiangmuzhongzenmotianjiaeslintdaimajianchajieslintjibenpeizhi.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 项目中怎么添加eslint代码检查及eslint基本配置 3 | date: 2017-12-11T09:32:30Z 4 | lastmod: 2017-12-11T09:40:31Z 5 | summary: 6 | tags: ["开发工具", "eslint"] 7 | draft: false 8 | layout: PostLayout 9 | bibliography: references-data.bib 10 | --- 11 | 12 | 前端开发到目前为止,已经是多团队,跨项目开发,那么在各个团队及各个项目来回切换的时候,怎么去提高我们的开发效率,显然规范化能够帮助我们解决这个问题,那么到目前为止eslint是一个很棒的js代码规范化选择,我们可以根据官方的规则来进行定制,也可以根据一些成型的规则来引入如standard等;每个团队可以根据自己公司的要求来进行选择,下面是如何在项目中引入eslint的步骤。 13 | 14 | **eslint安装有两种方式** 15 | 16 | 1. 使用全局安装 npm install -g eslint 17 | 2. 项目文件内安装 npm install --save-dev eslint 18 | 19 | 20 | **eslint使用方式,不配置package.json** 21 | 22 | 1. 初始化eslintrc.js文件,全局直接在项目目录内使用eslint --init 23 | 2. 项目内安装git 内使用./node_modules/.bin/eslint --init;cmd内使用 .\node_modules\.bin\eslint --init 24 | 3. 配置eslintrc.js文件,这里我们使用standard配置文件,然后在上面做些许修改 25 | 4. eslint进行代码检查,git上使用./node_modules/.bin/eslint 需要被检查的.js文件 26 | 27 | **eslint使用方式,进行package.json设置** 28 | 29 | 1. 在scripts对象内配置lint命令"lint": "eslint --ext .js,.vue src" 30 | 2. 运行npm run lint即可对src目录下所有.js与.vue后缀的文件进行检查 31 | 32 | **eslint修复错误代码** 33 | 34 | 1. 运行代码./node_modules/.bin/eslint --fix src or ./node_modules/.bin/eslint --fix src/utils/index.js 35 | 2. 在scripts内配置命令"fix": "eslint --fix src" or "fix": "eslint --ext .js,.vue --fix src" or "fix": "eslint --fix src/utils/index.js" 36 | 37 | **参考链接** 38 | https://github.com/eslint/eslint 39 | https://github.com/standard/standard 40 | http://eslint.cn/docs/rules/ 41 | -------------------------------------------------------------------------------- /pages/tags/[tag].tsx: -------------------------------------------------------------------------------- 1 | import { TagSEO } from '@/components/SEO' 2 | import siteMetadata from '@/data/siteMetadata' 3 | import ListLayout from '@/layouts/ListLayout' 4 | import { kebabCase } from 'pliny/utils/kebabCase' 5 | import { getAllTags, allCoreContent } from 'pliny/utils/contentlayer' 6 | import { InferGetStaticPropsType } from 'next' 7 | import { allBlogs } from 'contentlayer/generated' 8 | 9 | export async function getStaticPaths() { 10 | const tags = await getAllTags(allBlogs) 11 | 12 | return { 13 | paths: Object.keys(tags).map((tag) => ({ 14 | params: { 15 | tag, 16 | }, 17 | })), 18 | fallback: false, 19 | } 20 | } 21 | 22 | export const getStaticProps = async (context) => { 23 | const tag = context.params.tag as string 24 | const filteredPosts = allCoreContent( 25 | allBlogs.filter( 26 | (post) => post.draft !== true && post.tags.map((t) => kebabCase(t)).includes(tag) 27 | ) 28 | ) 29 | 30 | return { props: { posts: filteredPosts, tag } } 31 | } 32 | 33 | export default function Tag({ posts, tag }: InferGetStaticPropsType) { 34 | // Capitalize first letter and convert space to dash 35 | const title = tag[0].toUpperCase() + tag.split(' ').join('-').slice(1) 36 | return ( 37 | <> 38 | 42 | 43 | 44 | ) 45 | } 46 | -------------------------------------------------------------------------------- /components/Header.tsx: -------------------------------------------------------------------------------- 1 | import siteMetadata from '@/data/siteMetadata' 2 | import headerNavLinks from '@/data/headerNavLinks' 3 | import Link from './Link' 4 | import MobileNav from './MobileNav' 5 | import ThemeSwitch from './ThemeSwitch' 6 | 7 | const Header = () => { 8 | return ( 9 |
10 |
11 | 12 |
13 |
14 | 15 |
16 | {typeof siteMetadata.headerTitle === 'string' ? ( 17 |
18 | {siteMetadata.headerTitle} 19 |
20 | ) : ( 21 | siteMetadata.headerTitle 22 | )} 23 |
24 | 25 |
26 |
27 |
28 | {headerNavLinks.map((link) => ( 29 | 34 | {link.title} 35 | 36 | ))} 37 |
38 | 39 | 40 |
41 |
42 | ) 43 | } 44 | 45 | export default Header 46 | -------------------------------------------------------------------------------- /data/blog/hanshufangdou.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 函数防抖 3 | date: 2018-03-01T08:41:30Z 4 | lastmod: 2018-03-01T08:41:30Z 5 | summary: 6 | tags: ["原生JS", "防抖"] 7 | draft: false 8 | layout: PostLayout 9 | bibliography: references-data.bib 10 | --- 11 | 12 | 函数防抖应用于频繁触发的事件,防止浏览器崩溃,常见的应用事件有window对象的resize事件,scroll事件,还有就是mousedown,mousemove,keyup,keydown等事件 13 | 14 | ``` 15 |
16 | 17 | var box = document.getElementsByClassName("box")[0]; 18 | var WAIT_TIME = 500; 19 | 20 | //第一种方式 就是当move事件停止之后500ms触发 21 | var debounce1 = function (func, time){ 22 | var timeout; 23 | return function (){ 24 | var _this = this, 25 | args = arguments; 26 | clearTimeout(timeout); 27 | timeout = setTimeout(function (){ 28 | func.apply(_this, args); 29 | },time); 30 | } 31 | } 32 | 33 | var getUserAction = function (){ 34 | console.log(this); 35 | console.log(arguments); 36 | } 37 | 38 | // box.onmousemove = debounce1(getUserAction, WAIT_TIME); 39 | 40 | //第二种方式、当move事件开始的时候就触发,然后等到move事件停止后在执行一次 41 | var debounce2 = function (func, time, immediate){ 42 | var timeout; 43 | return function (){ 44 | var _this = this, 45 | args = arguments; 46 | if(timeout) clearTimeout(timeout); 47 | 48 | if(immediate){ 49 | var callNow = !timeout; 50 | timeout = setTimeout(function (){ 51 | timeout = null; 52 | func.apply(_this, args); //move停止之后执行 53 | },time); 54 | if(callNow) func.apply(_this, args); //第一次执行 55 | }else { 56 | timeout = setTimeout(function (){ 57 | func.apply(_this, args); 58 | },time); 59 | } 60 | 61 | } 62 | } 63 | 64 | box.onmousemove = debounce2(getUserAction, WAIT_TIME, true); 65 | ``` 66 | -------------------------------------------------------------------------------- /data/blog/CSS3shuxingbox-shadowdezhengqueshiyongzishi.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: CSS3属性box-shadow的正确使用姿势 3 | date: 2017-12-14T13:32:24Z 4 | lastmod: 2017-12-14T13:32:24Z 5 | summary: 6 | tags: ["CSS", "box-shadow"] 7 | draft: false 8 | layout: PostLayout 9 | bibliography: references-data.bib 10 | --- 11 | 12 | box-shadow虽然一直在使用,但是没有总结过,这几天在项目中频繁的用到,于是总结记录一番。 13 | 14 | box-shadow **CSS3** 的属性,目前兼容ie9+及现代浏览器,共有6个属性值,如下所示 15 | 16 | `box-shadow: outside|inside offset-x offset-y blur-radius spread-radius color` 17 | 18 | 1. 第一个参数 outside|inside 阴影位置显示参数,默认为outside(可以省略); 19 | 2. 第二个参数offset-x设置水平方向的阴影,正值表示水平右方向阴影,负值表示水平左方向阴影,0表示水平左右阴影,值越大阴影偏移元素的位置越远; 20 | 3. 第三个参数offset-y设置垂直方向的阴影,正值表示垂直下方向阴影,负值表示垂直上方向阴影,0表示垂直上下阴影,值越大阴影偏移元素的位置越远; 21 | 4. 第四个参数blur-radius设置阴影的模糊面积,只能设正值,值越大阴影越模糊; 22 | 5. 第五个参数spread-radius设置阴影扩大收缩参数,正值表示扩大,负值表示缩小,值越大阴影的面积就越大(即阴影的面积会超过元素本身的面积); 23 | 6. 第六个参数color设置阴影的颜色; 24 | 25 | 几种常用的场合 26 | 27 | 1. 设置4面阴影 `box-shadow: 0 0 10px 3px #ccc;`; 28 | ![image](https://user-images.githubusercontent.com/20950813/33994453-810b6d08-e115-11e7-93b4-30fd3bc64253.png) 29 | 30 | 2. 设置单边阴影如下边阴影,关键在spread-radius扩大收缩参数,这个时候需要设置成负值,不然水平方向会有阴影,其它同理`box-shadow: 0 10px 10px -5px #ccc;`; 31 | ![image](https://user-images.githubusercontent.com/20950813/33994583-0d0eb576-e116-11e7-8efd-dd27347c40a6.png) 32 | 33 | 3. 每边设置不同的阴影,需要注意的时候,因该是渲染了四次,只不过每组组侧重的阴影不一样,才能设置不同颜色的阴影,这里的blur-radius不能大,大的话阴影之间会互相渗透 34 | `box-shadow: 3px 0 1px red, -3px 0 10px #ccc, 0 -3px 1px blue, 0 3px 1px #000;` 35 | ![image](https://user-images.githubusercontent.com/20950813/33994570-ffa92362-e115-11e7-8429-e9b653875ffd.png) 36 | 37 | 38 | **参考链接** 39 | https://developer.mozilla.org/zh-CN/docs/Web/CSS/box-shadow 40 | -------------------------------------------------------------------------------- /components/ThemeSwitch.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react' 2 | import { useTheme } from 'next-themes' 3 | 4 | const ThemeSwitch = () => { 5 | const [mounted, setMounted] = useState(false) 6 | const { theme, setTheme, resolvedTheme } = useTheme() 7 | 8 | // When mounted on client, now we can show the UI 9 | useEffect(() => setMounted(true), []) 10 | 11 | return ( 12 | 34 | ) 35 | } 36 | 37 | export default ThemeSwitch 38 | -------------------------------------------------------------------------------- /pages/_document.tsx: -------------------------------------------------------------------------------- 1 | import Document, { Html, Head, Main, NextScript } from 'next/document' 2 | import siteMetadata from '@/data/siteMetadata' 3 | import Analytics from '@/components/GoogleAnalytics' 4 | 5 | const isProduction = process.env.NODE_ENV !== 'production' 6 | class MyDocument extends Document { 7 | render() { 8 | return ( 9 | 10 | 11 | 12 | 18 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | {isProduction && siteMetadata.analytics.googleAnalyticsId && } 31 | 32 | 33 |
34 | 35 | 36 | 37 | ) 38 | } 39 | } 40 | 41 | export default MyDocument 42 | -------------------------------------------------------------------------------- /components/Card.tsx: -------------------------------------------------------------------------------- 1 | import Image from './Image' 2 | import Link from './Link' 3 | 4 | const Card = ({ title, description, imgSrc, href }) => ( 5 |
6 |
11 | {imgSrc && 12 | (href ? ( 13 | 14 | {title} 21 | 22 | ) : ( 23 | {title} 30 | ))} 31 |
32 |

33 | {href ? ( 34 | 35 | {title} 36 | 37 | ) : ( 38 | title 39 | )} 40 |

41 |

{description}

42 | {href && ( 43 | 48 | Learn more → 49 | 50 | )} 51 |
52 |
53 |
54 | ) 55 | 56 | export default Card 57 | -------------------------------------------------------------------------------- /data/blog/jQueryshixianloading.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: jQuery实现loading 3 | date: 2018-03-01T08:21:18Z 4 | lastmod: 2019-07-06T13:45:53Z 5 | summary: 6 | tags: ["jQuery"] 7 | draft: false 8 | layout: PostLayout 9 | bibliography: references-data.bib 10 | --- 11 | 12 | ``` 13 | //页面的加载完毕可以使用onreadystatechange事件来进行dom结构的加载,通过readyState的状态值来进行判断,dom事件加载完毕,complate加载完成 14 | //interactive(交互)也就是正在加载的意思 15 | var complateLoading = function (){ 16 | if(document.readyState == "complete"){ 17 | var oMask = document.getElementById("mask"); 18 | oMask.style.display = "none"; 19 | } 20 | } 21 | 22 | var ajaxRequest = function (){ //这是第一种方式 就是利用beforesend方法(ajax请求前)来显示loading,success,error方法完成之后再none 掉loading 23 | var oMask = document.getElementById("mask"); 24 | $.ajax({ 25 | type:"get", 26 | url:"http://127.0.0.1:8020/小结文档/json/nes.json", 27 | data: "", 28 | beforeSend: function (){ 29 | oMask.style.display = "block"; 30 | }, 31 | success: function (data){ 32 | console.log(data); 33 | oMask.style.display = "none"; 34 | }, 35 | error: function (data){ 36 | console.log(data); 37 | oMask.style.display = "none"; 38 | } 39 | }); 40 | } 41 | 42 | var ajaxRequestAll = function (){ 43 | $.ajax({ 44 | type:"get", 45 | url:"http://127.0.0.1:8020/小结文档/json/new.json", 46 | data: "", 47 | success: function (data){ 48 | console.log(data); 49 | }, 50 | error: function (data){ 51 | console.log(data); 52 | } 53 | }); 54 | } 55 | 56 | $.ajaxSetup({ //第二个思路就是利用ajaxSetup来进行全局配置loading 57 | beforeSend: function (){ //请求之前的操作 58 | console.log("beforeSend"); 59 | var oMask = document.getElementById("mask"); 60 | oMask.style.display = "block"; 61 | }, 62 | complete: function (){ //不论请求成功还是失败 63 | console.log("complete"); 64 | var oMask = document.getElementById("mask"); 65 | oMask.style.display = "none"; 66 | } 67 | }); 68 | 69 | document.onreadystatechange = complateLoading; 70 | ``` 71 | 72 | -------------------------------------------------------------------------------- /pages/blog/page/[page].tsx: -------------------------------------------------------------------------------- 1 | import { PageSEO } from '@/components/SEO' 2 | import siteMetadata from '@/data/siteMetadata' 3 | import ListLayout from '@/layouts/ListLayout' 4 | import { allCoreContent, sortedBlogPost } from 'pliny/utils/contentlayer' 5 | import { POSTS_PER_PAGE } from '../index' 6 | import { InferGetStaticPropsType } from 'next' 7 | import { allBlogs } from 'contentlayer/generated' 8 | import type { Blog } from 'contentlayer/generated' 9 | 10 | export const getStaticPaths = async () => { 11 | const totalPosts = allBlogs 12 | const totalPages = Math.ceil(totalPosts.length / POSTS_PER_PAGE) 13 | const paths = Array.from({ length: totalPages }, (_, i) => ({ 14 | params: { page: (i + 1).toString() }, 15 | })) 16 | 17 | return { 18 | paths, 19 | fallback: false, 20 | } 21 | } 22 | 23 | export const getStaticProps = async (context) => { 24 | const { 25 | params: { page }, 26 | } = context 27 | const posts = sortedBlogPost(allBlogs) as Blog[] 28 | const pageNumber = parseInt(page as string) 29 | const initialDisplayPosts = posts.slice( 30 | POSTS_PER_PAGE * (pageNumber - 1), 31 | POSTS_PER_PAGE * pageNumber 32 | ) 33 | const pagination = { 34 | currentPage: pageNumber, 35 | totalPages: Math.ceil(posts.length / POSTS_PER_PAGE), 36 | } 37 | 38 | return { 39 | props: { 40 | initialDisplayPosts: allCoreContent(initialDisplayPosts), 41 | posts: allCoreContent(posts), 42 | pagination, 43 | }, 44 | } 45 | } 46 | 47 | export default function PostPage({ 48 | posts, 49 | initialDisplayPosts, 50 | pagination, 51 | }: InferGetStaticPropsType) { 52 | return ( 53 | <> 54 | 55 | 61 | 62 | ) 63 | } 64 | -------------------------------------------------------------------------------- /data/blog/jquery-barcode-JsBarcodetiaomashengchengchajian.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: jquery-barcode/JsBarcode条码生成插件 3 | date: 2018-03-01T07:59:37Z 4 | lastmod: 2018-03-01T08:01:34Z 5 | summary: 6 | tags: ["jQuery"] 7 | draft: false 8 | layout: PostLayout 9 | bibliography: references-data.bib 10 | --- 11 | 12 | ### jquery-barcode生成的条码插件 13 | ![image](https://user-images.githubusercontent.com/20950813/36833000-efcdab72-1d68-11e8-91cf-d6511d376eb6.png) 14 | 15 | ``` 16 | // code种类code11、code39、code93、code128、ean8、ean13、std25、int25、msi、datamatrix 17 | $("#bcTarget").barcode("156130510575933", "code128",{ 18 | barWidth: 2, //单条条码宽度(即最小条码宽度) 19 | barHeight: 50, //单体条码高度 20 | addQuietZone: false, //是否添加空白区(内边距) 21 | moduleSize: 5, 22 | showHRI: true, //是否显示底部条码描述 23 | marginHRI: 5, //底部条码的margin-top 24 | bgColor: "#FFF", //设置条码的背景颜色(包括底部的描述在内) 25 | color: "#000",//设置条码的字体颜色(包括底部的描述在内) 26 | fontSize: 10, 27 | output: "css", //渲染方式 css/bmp/svg/canvas 28 | posX: 10, //没什么用 29 | posY: 20 //没什么用 30 | }); 31 | ``` 32 | 33 | 34 | ### JsBarcode生成的条码插件 35 | ![jsbarcode](https://user-images.githubusercontent.com/20950813/36833023-02d4ee56-1d69-11e8-8299-0b53b054be29.png) 36 | 37 | ``` 38 | $canvas = $(""); 39 | $canvas.JsBarcode("PD170622000001", { //注意这个插件只支持img,canvas,svg这三种标签生成条形码 40 | format: "CODE128", //选择条码生成的类型,这里选code128表示一般商用 41 | lineColor: "#000", //条形码颜色 42 | width: 2, // 条码间距 43 | height: 40, // 条码高度 44 | text: "PD170622000001", 45 | fontSize: 14, 46 | displayValue: true, //是否显示条码下面的值 47 | font: "Arail", 48 | textAlign: "left", 49 | textPosition: "bottom", 50 | textMargin: 2, 51 | // background: #ccc, 52 | margin: 0, 53 | fontOptions: "bold", 54 | valid: function (){ //生成之后的回调 55 | console.log(111111); 56 | } 57 | }); 58 | $canvas.appendTo($("#box")); 59 | ``` 60 | 61 | 需要注意的是生成的条码得保证打印的时候不失真,不漏指针,保证扫描枪能够正常扫描,根据在项目中的使用,目前最好的方式是jquery-barcode生成bmp格式的条码是不会出现失真及漏针的情况! 62 | 63 | -------------------------------------------------------------------------------- /pages/tags.tsx: -------------------------------------------------------------------------------- 1 | import Link from '@/components/Link' 2 | import { PageSEO } from '@/components/SEO' 3 | import Tag from '@/components/Tag' 4 | import siteMetadata from '@/data/siteMetadata' 5 | import { kebabCase } from 'pliny/utils/kebabCase' 6 | import { getAllTags } from 'pliny/utils/contentlayer' 7 | import { GetStaticProps, InferGetStaticPropsType } from 'next' 8 | import { allBlogs } from 'contentlayer/generated' 9 | 10 | export const getStaticProps: GetStaticProps<{ tags: Record }> = async () => { 11 | const tags = await getAllTags(allBlogs) 12 | 13 | return { props: { tags } } 14 | } 15 | 16 | export default function Tags({ tags }: InferGetStaticPropsType) { 17 | const sortedTags = Object.keys(tags).sort((a, b) => tags[b] - tags[a]) 18 | return ( 19 | <> 20 | 21 |
22 |
23 |

24 | Tags 25 |

26 |
27 |
28 | {Object.keys(tags).length === 0 && 'No tags found.'} 29 | {sortedTags.map((t) => { 30 | return ( 31 |
32 | 33 | 38 | {` (${tags[t]})`} 39 | 40 |
41 | ) 42 | })} 43 |
44 |
45 | 46 | ) 47 | } 48 | -------------------------------------------------------------------------------- /data/blog/shenkaobeiyuqiankaobei.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 深拷贝与浅拷贝 3 | date: 2018-03-01T09:00:55Z 4 | lastmod: 2018-03-01T09:01:46Z 5 | summary: 6 | tags: ["原生JS", "拷贝"] 7 | draft: false 8 | layout: PostLayout 9 | bibliography: references-data.bib 10 | --- 11 | 12 | ### 简介 13 | 要了解深浅拷贝,首先要了解js里面的数据类型,js里面共有两种数据类型,第一类5种基本类型,第二类1种引用类型,二者的主要区别如下 14 | 1. 包含成员基本类型,包括number,string,boolean,undefined,null,引用类型包括obj 15 | 2. 存放位置,基本类型存放于栈区域,引用类型指针存放于栈区域,内容存放于堆区域 16 | 3. 值得可变性,基本类型的值不可变(注意与重新赋值的区别),引用类型的值是可变的; 17 | 4. 比较,基本类型的比较是值得比较(即值相等就可以判断这两个变量相等,建议使用严格等,避免变量进行隐试转换),引用类型的比较是引用的比较(即指针的比较),引用相等即引用的时同一个引用类型的数据 18 | 5. 拷贝,基本数据类型的拷贝即是重新复制给另一个变量,两个变量互不影响,引用类型的拷贝分为三类,第一类引用类型赋值,即指针的赋值,二者指向同一个引用类型,当其中一个改变时,另一个也会跟着改变,第二类浅拷贝,只拷贝一层,没有对对象中的子对象也进行拷贝,两个对象中的基本类型的值改变,不会互相影响,但是引用类型改变时会互相影响,第三类深拷贝,对对象及对象中包含的子对象进行递归拷贝,拷贝完之后的两个对象不管是基本类型的值还是引用类型的值改变都互不影响; 19 | 20 | 传值与传址的区别,两者都是针对变量在赋值的时候而言的,传值指的时值得传递(即基本类型的赋值),传址指的是引用的赋值(及引用类型的赋值) 21 | 22 | ### 浅拷贝 23 | ``` 24 | var shallowCopy = function (src){ 25 | var obj = {}; 26 | for(var prop in src){ 27 | if(src.hasOwnProperty(prop)){ 28 | obj[prop] = src[prop] 29 | } 30 | } 31 | return obj 32 | } 33 | ``` 34 | 35 | ### 深拷贝 36 | 深拷贝核心思想就是递归去复制所有的引用类型,然后在复制的时候需要区分下数组与对象,可以复制函数 37 | ``` 38 | // 第一种方式 39 | var deepCopy = function (source,target){ 40 | var c = target || {}; 41 | for(var prop in source){ 42 | if(typeof source[prop] === "object"){ 43 | if(source[prop].constructor === Array){ 44 | c[prop] = []; 45 | }else if (source[prop].constructor === Object){ 46 | c[prop] = {}; 47 | } 48 | deepCopy(source[prop],c[prop]); 49 | }else { 50 | c[prop] = source[prop] 51 | } 52 | } 53 | return c 54 | } 55 | 56 | // 第二种方式 57 | var deepCopy2 = function (obj) { 58 | return JSON.parse(JSON.stringify(obj)); 59 | } 60 | 61 | // 第三种方式与第一种一样只是换个写法 62 | var deepCopy3 = function(obj) { 63 | if (typeof obj !== 'object') return; 64 | var newObj = obj instanceof Array ? [] : {}; 65 | for (var key in obj) { 66 | if (obj.hasOwnProperty(key)) { 67 | newObj[key] = typeof obj[key] === 'object' ? deepCopy3(obj[key]) : obj[key]; 68 | } 69 | } 70 | return newObj; 71 | } 72 | 73 | ``` 74 | 75 | 76 | -------------------------------------------------------------------------------- /layouts/AuthorLayout.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode } from 'react' 2 | import type { Authors } from 'contentlayer/generated' 3 | import SocialIcon from '@/components/social-icons' 4 | import Image from '@/components/Image' 5 | import { PageSEO } from '@/components/SEO' 6 | 7 | interface Props { 8 | children: ReactNode 9 | content: Omit 10 | } 11 | 12 | export default function AuthorLayout({ children, content }: Props) { 13 | const { name, avatar, occupation, company, email, twitter, linkedin, github } = content 14 | 15 | return ( 16 | <> 17 | 18 |
19 |
20 |

21 | About 22 |

23 |
24 |
25 |
26 | avatar 33 |

{name}

34 |
{occupation}
35 |
{company}
36 |
37 | 38 | 39 | 40 | 41 |
42 |
43 |
{children}
44 |
45 |
46 | 47 | ) 48 | } 49 | -------------------------------------------------------------------------------- /data/blog/vuexiangmuzhongyinruechart.jslaizhizuotubiao.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: vue项目中引入echart.js来制作图表 3 | date: 2017-12-11T12:44:05Z 4 | lastmod: 2017-12-11T12:50:46Z 5 | summary: 6 | tags: ["前端框架", "vue"] 7 | draft: false 8 | layout: PostLayout 9 | bibliography: references-data.bib 10 | --- 11 | 12 | echart.js是一款超级强大的图表制作插件,可以满足我们展示业务数据,如柱形图,饼状图,折线图,仪表图等,因为这次项目内大量地方用到数据展示,所以引入了echart.js插件用于制作图表,先总结下开发的时候遇到的小问题 13 | 14 | 1. 首先引入echarts.js,使用npm install --save echarts 15 | 16 | 2. 定制组件,因为复用的地方比较多,所以抽出来成了组件,共封装了四个组件,折线图,柱状图,饼状图,仪表图,封装这四个组件的方法类似 17 | 18 | 3. 按需引入,因为echarts的组件较多导致文件较大,所以这里需要使用什么就引入什么,bar柱状图,gauge仪表盘,line折线图,pie饼状图 19 | `const echarts = require('echarts/lib/echarts'); // 引入echarts主模块 20 | require('echarts/lib/chart/bar'); // 引入柱状图 21 | require('echarts/lib/component/tooltip'); // 引入提示框 22 | require('echarts/lib/component/title'); // 引入标题组件 23 | require('echarts/theme/macarons'); // echarts 主题` 24 | 25 | 4. 柱状图通过xAxis,与yAxis的type属性的值来决定哪个是类目轴哪个是数值轴,category类目轴,value表示数值轴 26 | ` xAxis: { 27 | // type参数决定那个是类目轴哪个是数值轴 28 | type: this.isShowX ? 'category' : 'value', 29 | },` 30 | `yAxis: { 31 | type: this.isShowX ? 'value' : 'category', 32 | },` 33 | 34 | 5. 怎样让echart.js制作出来的图表随着浏览器的窗口大小的变化而变化 35 | 1. 初始化组件的时候,监听window对象的resize事件,回调函数是chart对象的返回值下的resize方法,echart插件自带的方法 36 | `window.addEventListener('resize', this.chart.resize)` 37 | 2. 销毁组件的时候在注销掉resize事件 38 | ` window.removeEventListener('resize', this.chart.resize);`,注意要在this.chart.dispose();之前注销 39 | 40 | 6. 怎么去动态设置图表的值,让图表随着后端请求来的值进行实时更新 41 | 1. watch父组件传入的数据 42 | 2. this.chart.setOption(this.opt); 利用setOption方法重新赋值 43 | 44 | 7. 柱状图,折线图不显示坐标轴,通过设置xAxis,yAxis下的axisLine,不显示背景色通过设置xAxis,yAxis下的splitLine与splitArea 45 | 46 | 8. legend属性用于设置切换索引 47 | 48 | 开发效果图 49 | ![chart1](https://user-images.githubusercontent.com/20950813/33831586-989628d2-deb3-11e7-97f0-5dd1a1751449.png) 50 | 51 | ![chart3](https://user-images.githubusercontent.com/20950813/33831672-ee53252c-deb3-11e7-96e2-4ed0cc086022.png) 52 | 53 | 54 | echart.js的使用不难,难的时配置参数的查找; 55 | 56 | 封装有该组件的项目地址 57 | 58 | 59 | 参考链接 60 | http://echarts.baidu.com/option.html#series 61 | 62 | -------------------------------------------------------------------------------- /data/blog/CSS3shuxingborder-radiusdezhengqueshiyongzishi.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: CSS3属性border-radius的正确使用姿势 3 | date: 2017-12-14T13:55:43Z 4 | lastmod: 2017-12-14T13:57:36Z 5 | summary: 6 | tags: ["CSS", "border-radius"] 7 | draft: false 8 | layout: PostLayout 9 | bibliography: references-data.bib 10 | --- 11 | 12 | border-radius也是经常使用的一个CSS3属性,最近也经常在项目中使用,所以总结记录一番; 13 | 14 | border-radius **CSS3**属性,兼容ie9+及现代浏览器,共有2个属性值,如下所示 15 | 16 | `border-radius: {1,4} [ / {1,4} 半径的第一个语法取值可取1~4个值 17 | 半径的第二个语法取值也可取1~4个值` 18 | 19 | 常用的几种写法 20 | 21 | 1. 一个属性值 border-radius: 20px; => border-radius: 20px 20px 20px 20px / 20px 20px 20px 20px; 22 | ![image](https://user-images.githubusercontent.com/20950813/33995444-ff210916-e118-11e7-8d75-3fb3867a12f2.png) 23 | 24 | 2. 两个属性值 border-radius: 20px 10px; => border-radius: 20px 10px 20px 10px / 20px 10px 20px 10px; 25 | ![image](https://user-images.githubusercontent.com/20950813/33995451-0761df42-e119-11e7-8045-7534cc48b471.png) 26 | 27 | 3. 三个属性值 border-radius: 20px 10px 5px; => border-radius: 20px 10px 5px 10px / 20px 10px 5px 10px; 28 | ![image](https://user-images.githubusercontent.com/20950813/33995457-0d3b614a-e119-11e7-9d41-fe39711d33dc.png) 29 | 30 | 4. 四个属性值 border-radius: 20px 10px 5px 15px; => border-radius: 20px 10px 5px 15px / 20px 10px 5px 15px; 31 | ![image](https://user-images.githubusercontent.com/20950813/33995468-1332b42c-e119-11e7-8470-021232ac0d6d.png) 32 | 33 | 5. 设置两组属性值 border-radius: 40px 10px 5px 15px / 10px 5px 25px 10px; (40/10左上角, 10/5右上角, 5/25右下角, 15/10左下角),每个值代表的意思如下所示40px(左上上边半径) 10px(右上上边半径) 5px(右下下边半径) 15px(左下下边半径) / 10px(左上左边半径) 5px(右上右边半径) 25px(右下右边半径) 10px(左下左边半径);第一组值要么是上边要么是下边,第二组值要么是左边要么是右边; 34 | ![image](https://user-images.githubusercontent.com/20950813/33995582-599387f2-e119-11e7-8936-2377fb3091b6.png) 35 | 36 | 37 | 6. 不使用简写属性 38 | `border-top-left-radius: 40px; => border-top-left-radius: 40px 40px; 39 | border-top-right-radius:20px; 40 | border-bottom-right-radius: 30px; 41 | border-bottom-left-radius: 40px;` 42 | ![image](https://user-images.githubusercontent.com/20950813/33995587-5e144532-e119-11e7-89d6-9ce057679873.png) 43 | 44 | **总结:理解一点一个元素总共可以设置4个圆角,而一个圆角是需要两个值来进行设置的** 45 | 46 | 参考链接 47 | https://developer.mozilla.org/zh-CN/docs/Web/CSS/border-radius 48 | 49 | -------------------------------------------------------------------------------- /data/blog/xiugainpmjiyarndeyuan.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 修改npm及yarn的源 3 | date: 2019-07-16T03:21:57Z 4 | lastmod: 2022-10-06T17:50:17Z 5 | summary: 6 | tags: ["开发工具", "npm", "yarn"] 7 | draft: false 8 | layout: PostLayout 9 | images: ['/static/images/banner/yarn.png'] 10 | bibliography: references-data.bib 11 | --- 12 | 13 | ## npm及yarn源的简单修改 14 | 15 | ### npm 16 | 17 | 设置单次包下载的源,以淘宝镜像为例 18 | 19 | ```shell 20 | npm install package --registry https://registry.npm.taobao.org 21 | ``` 22 | 23 | 设置全局镜像,以淘宝镜像为例 24 | 25 | ```shell 26 | 获取npm当前的源 27 | npm config get registry 28 | 29 | 设置npm当前的源 30 | npm config set registry https://registry.npm.taobao.org 31 | ``` 32 | 33 | ### yarn 34 | 35 | 设置单次包下载的源,以淘宝镜像为例 36 | 37 | ```shell 38 | yarn add package --registry https://registry.npm.taobao.org 39 | ``` 40 | 41 | 设置全局镜像,以淘宝镜像为例 42 | 43 | ```shell 44 | 获取yarn当前的源 45 | yarn config get registry 46 | 47 | 设置yarn当前的源 48 | yarn config set registry https://registry.npm.taobao.org 49 | ``` 50 | 51 | ### 借助第三方工具实现快速切换npm及yarn源 52 | 53 | npm借助nrm来实现 54 | 55 | 安装 56 | ```shell 57 | yarn global add nrm || npm install -g nrm 58 | ``` 59 | 60 | 列出可选的源,带 * 的是当前使用的源 61 | ```shell 62 | nrm ls 63 | ``` 64 | ![image](https://user-images.githubusercontent.com/20950813/61263472-8d213f00-a7bb-11e9-89ec-fec5e49564cf.png) 65 | 66 | 67 | 显示当前的源 68 | ```shell 69 | nrm current 70 | ``` 71 | 72 | 切换源 73 | 74 | ```shell 75 | nrm use taobao 76 | ``` 77 | 78 | 添加源 79 | 80 | ```shell 81 | nrm add xxx https://registry-npm.xxx.com.cn/ 82 | ``` 83 | ![image](https://user-images.githubusercontent.com/20950813/61263484-9ad6c480-a7bb-11e9-8986-6ac3b5e19cdc.png) 84 | 85 | 86 | 删除源 87 | 88 | ```shell 89 | nrm del xxx 90 | ``` 91 | ![image](https://user-images.githubusercontent.com/20950813/61263498-a7f3b380-a7bb-11e9-8f15-423717b42aea.png) 92 | 93 | 94 | 测试响应时间 95 | 96 | ```shell 97 | nrm test 98 | ``` 99 | ![image](https://user-images.githubusercontent.com/20950813/61263541-dc676f80-a7bb-11e9-99f6-d1645831627c.png) 100 | 101 | yarn借助yrm来实现 102 | yrm是nrmfork出来的,使用方式与nrm保持一致 103 | 区别是使用yrm use 切换源时,会同时切换npm及yarn的源,而nrm则只会切换npm的源 104 | 另外nrm与yrm不能共存,先全局装了yrm,然后又全局装了nrm,导致yrm不能使用了 105 | 106 | 107 | ## 参考链接 108 | https://github.com/Pana/nrm 109 | https://github.com/i5ting/yrm 110 | -------------------------------------------------------------------------------- /css/SlugList.css: -------------------------------------------------------------------------------- 1 | ul[role='slug-list']:empty { 2 | margin: 0 !important; 3 | padding: 0 !important; 4 | } 5 | 6 | ul[role='slug-list'] li > a.active { 7 | color: rgb(99 102 241 / var(--tw-text-opacity)); 8 | } 9 | 10 | ul[role='slug-list'] li[data-depth='2'] { 11 | padding-left: 12px; 12 | } 13 | 14 | ul[role='slug-list'] li[data-depth='3'] { 15 | padding-left: 24px; 16 | } 17 | 18 | .default-layout-toc { 19 | background-color: #fff; 20 | -webkit-box-sizing: content-box; 21 | box-sizing: content-box; 22 | list-style: none; 23 | margin: 0; 24 | max-height: calc(90vh - 80px); 25 | overflow: auto; 26 | padding: 0 24px 0 0; 27 | position: fixed; 28 | right: 0; 29 | top: 50px; 30 | width: 186px; 31 | z-index: 10; 32 | } 33 | 34 | [data-prefers-color=dark] .default-layout-toc { 35 | background-color: #141414; 36 | } 37 | @media only screen and (max-width: 767px) { 38 | .default-layout-toc { 39 | display: none; 40 | } 41 | } 42 | .default-layout-toc li { 43 | position: relative; 44 | margin: 0; 45 | padding: 4px 0 4px 6px; 46 | text-indent: 12px; 47 | font-size: 13px; 48 | line-height: 1.40625; 49 | white-space: nowrap; 50 | text-overflow: ellipsis; 51 | overflow: hidden; 52 | } 53 | .default-layout-toc li a { 54 | color: #454d64; 55 | text-decoration: none; 56 | } 57 | [data-prefers-color=dark] .default-layout-toc li a { 58 | color: rgba(255, 255, 255, 0.85); 59 | } 60 | .default-layout-toc li a::before { 61 | content: ''; 62 | position: absolute; 63 | top: 0; 64 | left: 0; 65 | bottom: 0; 66 | display: inline-block; 67 | width: 2px; 68 | background: #ebedf1; 69 | } 70 | .default-layout-toc li a:hover { 71 | color: rgb(79 70 229 / var(--tw-text-opacity));; 72 | } 73 | [data-prefers-color=dark] .default-layout-toc li a:hover { 74 | color: #f86437; 75 | } 76 | .default-layout-toc li a:active { 77 | color: #f7673c; 78 | } 79 | [data-prefers-color=dark] .default-layout-toc li a:active { 80 | color: #f85c2d; 81 | } 82 | .default-layout-toc li a.active { 83 | color: #f65c2d; 84 | } 85 | [data-prefers-color=dark] .default-layout-toc li a.active { 86 | color: #f7511e; 87 | } 88 | .default-layout-toc li a.active::before { 89 | background: rgb(99 102 241 / var(--tw-text-opacity)); 90 | } 91 | [data-prefers-color=dark] .default-layout-toc li a.active::before { 92 | background: #f7511e; 93 | } -------------------------------------------------------------------------------- /components/ScrollTopAndComment.tsx: -------------------------------------------------------------------------------- 1 | import siteMetadata from '@/data/siteMetadata' 2 | import { useEffect, useState } from 'react' 3 | 4 | const ScrollTopAndComment = () => { 5 | const [show, setShow] = useState(false) 6 | 7 | useEffect(() => { 8 | const handleWindowScroll = () => { 9 | if (window.scrollY > 50) setShow(true) 10 | else setShow(false) 11 | } 12 | 13 | window.addEventListener('scroll', handleWindowScroll) 14 | return () => window.removeEventListener('scroll', handleWindowScroll) 15 | }, []) 16 | 17 | const handleScrollTop = () => { 18 | window.scrollTo({ top: 0 }) 19 | } 20 | const handleScrollToComment = () => { 21 | document.getElementById('comment').scrollIntoView() 22 | } 23 | return ( 24 | 56 | ) 57 | } 58 | 59 | export default ScrollTopAndComment 60 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [掘金地址](https://juejin.cn/user/3650034333393575/posts) 2 | 3 | ### 简介 4 | 枯藤老树昏鸦,小桥流水人家,古道西风瘦马,撸码人在天涯 5 | 6 | ### 博客分类 7 | 博客主要分以下几大类 8 | - [开发工具](https://github.com/willson-wang/Blog/projects/2) 9 | - [WEB综合](https://github.com/willson-wang/Blog/projects/6) 10 | - [前端框架](https://github.com/willson-wang/Blog/projects/1) 11 | - [CSS](https://github.com/willson-wang/Blog/projects/3) 12 | - [JavaScript](https://github.com/willson-wang/Blog/projects/5) 13 | 14 | 15 | ### 前端工具 16 | 17 | - [vite原理与实践记录](https://github.com/willson-wang/Blog/issues/101) 18 | - [.babelrc与babel.config.js配置文件的区别](https://github.com/willson-wang/Blog/issues/100) 19 | - [借助rollup构建npm包最佳实践](https://github.com/willson-wang/Blog/issues/99) 20 | - [lerna管理Monorepo项目实践](https://github.com/willson-wang/Blog/issues/97) 21 | - [了解ESLint各个parser之间的关系](https://github.com/willson-wang/Blog/issues/96) 22 | - [Tapable源码浅析](https://github.com/willson-wang/Blog/issues/95) 23 | 24 | 25 | ### WEB综合 26 | - [浏览器内的事件循环机制及macrotask与microtask 浏览器](https://github.com/willson-wang/Blog/issues/7) 27 | - [HTML5、HTTP、WEB综合汇总](https://github.com/willson-wang/Blog/issues/20) 28 | - [记录一次php、apache、mysql、composer、wampserver安装过程](https://github.com/willson-wang/Blog/issues/31) 29 | - [cookie小结](https://github.com/willson-wang/Blog/issues/38) 30 | - [node shell pre-commit eslint prettier](https://github.com/willson-wang/Blog/issues/48) 31 | - [bash shell pre-commit eslint prettier](https://github.com/willson-wang/Blog/issues/49) 32 | 33 | 34 | ### 前端框架 35 | - [vue项目中引入echart.js来制作图表](https://github.com/willson-wang/Blog/issues/3) 36 | - [vuex的使用总结](https://github.com/willson-wang/Blog/issues/6) 37 | - [vue项目中怎么引入tinymce富文本编辑器](https://github.com/willson-wang/Blog/issues/11) 38 | - [vue项目中怎么引入mockjs](https://github.com/willson-wang/Blog/issues/12) 39 | - [vue开发小结](https://github.com/willson-wang/Blog/issues/14) 40 | - [vue响应式原理](https://github.com/willson-wang/Blog/issues/15) 41 | 42 | ### JavaScript 43 | - [正则及常用正则表达式](https://github.com/willson-wang/Blog/issues/13) 44 | - [常用设计模式](https://github.com/willson-wang/Blog/issues/21) 45 | - [函数防抖](https://github.com/willson-wang/Blog/issues/25) 46 | - [函数节流](https://github.com/willson-wang/Blog/issues/26) 47 | - [深拷贝与浅拷贝](https://github.com/willson-wang/Blog/issues/27) 48 | - [数组去重、数组去重并寻找最大项、数组排序](https://github.com/willson-wang/Blog/issues/28) 49 | - [Promise/Generator/Async相关的异步操作](https://github.com/willson-wang/Blog/issues/37) 50 | 51 | -------------------------------------------------------------------------------- /pages/blog/[...slug].tsx: -------------------------------------------------------------------------------- 1 | import { MDXLayoutRenderer } from 'pliny/mdx-components' 2 | import PageTitle from '@/components/PageTitle' 3 | import { MDXComponents } from '@/components/MDXComponents' 4 | import { sortedBlogPost, coreContent } from 'pliny/utils/contentlayer' 5 | import { InferGetStaticPropsType } from 'next' 6 | import { allBlogs, allAuthors } from 'contentlayer/generated' 7 | import type { Blog } from 'contentlayer/generated' 8 | 9 | const DEFAULT_LAYOUT = 'PostLayout' 10 | 11 | export async function getStaticPaths() { 12 | return { 13 | paths: allBlogs.map((p) => ({ params: { slug: p.slug.split('/') } })), 14 | fallback: false, 15 | } 16 | } 17 | 18 | export const getStaticProps = async ({ params }) => { 19 | const slug = (params.slug as string[]).join('/') 20 | const sortedPosts = sortedBlogPost(allBlogs) as Blog[] 21 | const postIndex = sortedPosts.findIndex((p) => p.slug === slug) 22 | const prevContent = sortedPosts[postIndex + 1] || null 23 | const prev = prevContent ? coreContent(prevContent) : null 24 | const nextContent = sortedPosts[postIndex - 1] || null 25 | const next = nextContent ? coreContent(nextContent) : null 26 | const post = sortedPosts.find((p) => p.slug === slug) 27 | const authorList = post.authors || ['default'] 28 | const authorDetails = authorList.map((author) => { 29 | const authorResults = allAuthors.find((p) => p.slug === author) 30 | return coreContent(authorResults) 31 | }) 32 | 33 | return { 34 | props: { 35 | post, 36 | authorDetails, 37 | prev, 38 | next, 39 | }, 40 | } 41 | } 42 | 43 | export default function BlogPostPage({ 44 | post, 45 | authorDetails, 46 | prev, 47 | next, 48 | }: InferGetStaticPropsType) { 49 | return ( 50 | <> 51 | {'draft' in post && post.draft === true ? ( 52 |
53 | 54 | Under Construction{' '} 55 | 56 | 🚧 57 | 58 | 59 |
60 | ) : ( 61 | 70 | )} 71 | 72 | ) 73 | } 74 | -------------------------------------------------------------------------------- /data/blog/npm-yarnchangyongmingling.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: npm && yarn常用命令 3 | date: 2019-11-24T07:11:31Z 4 | lastmod: 2023-03-25T04:08:35Z 5 | summary: 6 | tags: ["包管理工具", "yarn", "npm"] 7 | draft: false 8 | layout: PostLayout 9 | images: ['/static/images/banner/npm.png'] 10 | bibliography: references-data.bib 11 | --- 12 | 13 | ## 创建package.json 14 | 15 | | npm | yarn | 16 | |-- | --- | 17 | | npm init or npm init -y | yarn init or yarn init -y | 18 | 19 | ## 安装模块 20 | 21 | |npm | yarn | 22 | |-- | ---- | 23 | | npm install lodash or npm i lodash or npm i lodash --save | yarn add lodash | 24 | | npm install webpack --save-dev | yarn add webpack --dev | 25 | | npm install webpack -D | yarn add webpack -D | 26 | | npm i qs@6.0.0 | yarn add qs@6.0.0 | 27 | | npm i react react-dom prop-types --save | yarn add react react-dom prop-types | 28 | | npm i `@babel/{core,cli}` --save-dev | yarn add `@babel/{core,cli}` --dev | 29 | | npm i npm-checked -g | yarn global add npm-checked | 30 | 31 | ## 卸载模块 32 | 33 | | npm | yarn | 34 | | -- | -- | 35 | | npm uninstall webpack | yarn remove webpack | 36 | | npm uninstall react react-dom prop-types | yarn remove react react-dom prop-types | 37 | | npm uninstall `@babel/{core,cli}` | yarn remove `@babel/{core,cli}` | 38 | 39 | ## 获取安装包信息 40 | 41 | | npm | yarn | 42 | | -- | -- | 43 | | npm view react | yarn info react | 44 | | npm view react version | yarn info react version | 45 | | npm view react versions | yarn info react versions | 46 | | npm view react readme | yarn info react readme | 47 | 48 | ## 搜索安装包 49 | 50 | | npm | yarn | 51 | | -- | -- | 52 | | 53 | 54 | 55 | ## 依赖枚举 56 | 57 | | npm | yarn | 58 | | -- | -- | 59 | | npm list or npm ls | yarn ls | 60 | | npm list --depth=0 | yarn list --depth=0 | 61 | | | yarn list --depth=0 --pattern=md5 | 62 | | | yarn list --depth=0 --pattern="md5|webpack" | 63 | | npm ls -g --depth=0 | | 64 | 65 | ## 查看最新依赖 66 | 67 | | npm | yarn | 68 | | -- | -- | 69 | | npm outdate | yarn outdated | 70 | | npm outdate qs | yarn outdated qs | 71 | 72 | ## 更新依赖 73 | 74 | | npm | yarn | 75 | | -- | -- | 76 | | npm outdate | yarn outdated | 77 | | npm update | yarn upgrade | 78 | | npm update qs | yarn upgrade qs@6.9.1 | 79 | 80 | 注意yarn upgrade不会更新package.json内依赖的版本号,解决方法可参考[issues](https://github.com/yarnpkg/yarn/issues/2042) 81 | 82 | ## 本地软链接 83 | 84 | | npm | yarn | 85 | | -- | -- | 86 | | npm link && cd app && npm link xxx | yarn link && cd app && yarn link xxx | 87 | | | yarn unlink && cd app && yarn unlink xxx | 88 | 89 | 90 | -------------------------------------------------------------------------------- /pages/index.tsx: -------------------------------------------------------------------------------- 1 | import Link from '@/components/Link' 2 | import { PageSEO } from '@/components/SEO' 3 | import Tag from '@/components/Tag' 4 | import siteMetadata from '@/data/siteMetadata' 5 | import { formatDate } from 'pliny/utils/formatDate' 6 | import { sortedBlogPost, allCoreContent } from 'pliny/utils/contentlayer' 7 | import { InferGetStaticPropsType } from 'next' 8 | import { NewsletterForm } from 'pliny/ui/NewsletterForm' 9 | import { allBlogs } from 'contentlayer/generated' 10 | import type { Blog } from 'contentlayer/generated' 11 | import Article from '@/components/Article' 12 | import Banner from '@/components/Banner' 13 | 14 | const MAX_DISPLAY = 5 15 | 16 | export const getStaticProps = async () => { 17 | const sortedPosts = sortedBlogPost(allBlogs) as Blog[] 18 | const posts = allCoreContent(sortedPosts) 19 | 20 | return { props: { posts } } 21 | } 22 | 23 | export default function Home({ posts }: InferGetStaticPropsType) { 24 | return ( 25 | <> 26 | 27 | 28 |
29 |
30 |

31 | 最新文章 32 |

33 |

34 | {siteMetadata.description} 35 |

36 |
37 |
    38 | {!posts.length && '暂无数据'} 39 | {posts.slice(0, MAX_DISPLAY).map((frontMatter) => { 40 | const { slug } = frontMatter 41 | // @ts-ignore 42 | return
    43 | })} 44 |
45 |
46 | {posts.length > MAX_DISPLAY && ( 47 |
48 | 53 | 全部文章 → 54 | 55 |
56 | )} 57 | {siteMetadata.newsletter.provider && ( 58 |
59 | 60 |
61 | )} 62 | 63 | ) 64 | } 65 | -------------------------------------------------------------------------------- /css/docsearch.css: -------------------------------------------------------------------------------- 1 | 2 | .light .DocSearch { 3 | --docsearch-primary-color: theme(colors.primary.600); 4 | --docsearch-highlight-color: theme(colors.primary.600); 5 | --docsearch-searchbox-shadow: inset 0 0 0 2px theme(colors.primary.600); 6 | --docsearch-muted-color: theme(colors.gray.500); 7 | --docsearch-container-background: theme(colors.gray.400 / 80%); 8 | /* Modal */ 9 | --docsearch-modal-background: theme(colors.gray.200); 10 | /* Search box */ 11 | --docsearch-searchbox-background: theme(colors.gray.100); 12 | --docsearch-searchbox-focus-background: theme(colors.gray.100); 13 | /* Hit */ 14 | --docsearch-hit-color: theme(colors.gray.700); 15 | --docsearch-hit-shadow: none; 16 | --docsearch-hit-active-color: theme(colors.gray.800); 17 | --docsearch-hit-background: theme(colors.gray.100); 18 | /* Footer */ 19 | --docsearch-footer-background: theme(colors.gray.100); 20 | } 21 | 22 | .dark .DocSearch { 23 | --docsearch-primary-color: theme(colors.primary.600); 24 | --docsearch-highlight-color: theme(colors.primary.600); 25 | --docsearch-searchbox-shadow: inset 0 0 0 2px theme(colors.primary.600); 26 | --docsearch-text-color: theme(colors.gray.200); 27 | --docsearch-muted-color: theme(colors.gray.400); 28 | --docsearch-container-background: theme(colors.gray.900 / 80%); 29 | /* Modal */ 30 | --docsearch-modal-background: theme(colors.gray.900); 31 | --docsearch-modal-shadow: inset 1px 1px 0 0 rgb(44, 46, 64), 32 | 0 3px 8px 0 rgb(0, 3, 9); 33 | /* Search box */ 34 | --docsearch-searchbox-background: theme(colors.gray.800); 35 | --docsearch-searchbox-focus-background: theme(colors.gray.800); 36 | /* Hit */ 37 | --docsearch-hit-color: theme(colors.gray.200); 38 | --docsearch-hit-shadow: none; 39 | --docsearch-hit-active-color: theme(colors.gray.100); 40 | --docsearch-hit-background: theme(colors.gray.800); 41 | /* Footer */ 42 | --docsearch-footer-background: theme(colors.gray.900); 43 | --docsearch-footer-shadow: inset 0 1px 0 0 rgba(73, 76, 106, 0.5), 44 | 0 -4px 8px 0 rgba(0, 0, 0, 0.2); 45 | --docsearch-key-gradient: linear-gradient(-26.5deg, 46 | theme(colors.gray.800) 0%, 47 | theme(colors.gray.900) 100%); 48 | --docsearch-key-shadow: inset 0 -2px 0 0 rgb(40, 45, 85), 49 | inset 0 0 1px 1px rgb(81, 87, 125), 0 2px 2px 0 rgba(3, 4, 9, 0.3); 50 | --docsearch-logo-color: theme(colors.gray.300); 51 | } 52 | 53 | .light .DocSearch-Input { 54 | @apply hover:ring-0 ring-0; 55 | } 56 | 57 | .dark .DocSearch-Input { 58 | @apply hover:ring-0 ring-0; 59 | } 60 | 61 | .DocSearch-Container { 62 | @apply backdrop-blur; 63 | } 64 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | const { withContentlayer } = require('next-contentlayer') 2 | 3 | const withBundleAnalyzer = require('@next/bundle-analyzer')({ 4 | enabled: process.env.ANALYZE === 'true', 5 | }) 6 | 7 | // You might need to insert additional domains in script-src if you are using external services 8 | const ContentSecurityPolicy = ` 9 | default-src 'self'; 10 | script-src 'self' 'unsafe-eval' 'unsafe-inline' giscus.app www.googletagmanager.com www.google-analytics.com; 11 | style-src 'self' 'unsafe-inline'; 12 | img-src * blob: data:; 13 | media-src 'none'; 14 | connect-src *; 15 | font-src 'self'; 16 | frame-src giscus.app 17 | ` 18 | 19 | const securityHeaders = [ 20 | // https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP 21 | { 22 | key: 'Content-Security-Policy', 23 | value: ContentSecurityPolicy.replace(/\n/g, ''), 24 | }, 25 | // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy 26 | { 27 | key: 'Referrer-Policy', 28 | value: 'strict-origin-when-cross-origin', 29 | }, 30 | // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options 31 | { 32 | key: 'X-Frame-Options', 33 | value: 'DENY', 34 | }, 35 | // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options 36 | { 37 | key: 'X-Content-Type-Options', 38 | value: 'nosniff', 39 | }, 40 | // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-DNS-Prefetch-Control 41 | { 42 | key: 'X-DNS-Prefetch-Control', 43 | value: 'on', 44 | }, 45 | // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security 46 | { 47 | key: 'Strict-Transport-Security', 48 | value: 'max-age=31536000; includeSubDomains', 49 | }, 50 | // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Feature-Policy 51 | { 52 | key: 'Permissions-Policy', 53 | value: 'camera=(), microphone=(), geolocation=()', 54 | }, 55 | ] 56 | 57 | /** 58 | * @type {import('next/dist/next-server/server/config').NextConfig} 59 | **/ 60 | module.exports = () => { 61 | const plugins = [withContentlayer, withBundleAnalyzer] 62 | return plugins.reduce((acc, next) => next(acc), { 63 | reactStrictMode: true, 64 | pageExtensions: ['ts', 'tsx', 'js', 'jsx', 'md', 'mdx'], 65 | eslint: { 66 | dirs: ['pages', 'components', 'lib', 'layouts', 'scripts'], 67 | }, 68 | async headers() { 69 | return [ 70 | { 71 | source: '/(.*)', 72 | headers: securityHeaders, 73 | }, 74 | ] 75 | }, 76 | webpack: (config, options) => { 77 | config.module.rules.push({ 78 | test: /\.svg$/, 79 | use: ['@svgr/webpack'], 80 | }) 81 | 82 | return config 83 | }, 84 | }) 85 | } 86 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "my-blog", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "start": "next dev", 7 | "pull": "cross-env NODE_ENV=local node scripts/github", 8 | "dev": "cross-env INIT_CWD=$PWD next dev", 9 | "build": "cross-env INIT_CWD=$PWD next build && cross-env NODE_OPTIONS='--experimental-json-modules' node -r esbuild-register ./scripts/postbuild.mjs", 10 | "serve": "next start", 11 | "analyze": "cross-env ANALYZE=true next build", 12 | "lint": "next lint --fix --dir pages --dir components --dir lib --dir layouts --dir scripts" 13 | }, 14 | "dependencies": { 15 | "@next/bundle-analyzer": "13.1.6", 16 | "@next/font": "13.1.6", 17 | "@octokit/rest": "^19.0.7", 18 | "@tailwindcss/forms": "^0.5.3", 19 | "@tailwindcss/typography": "^0.5.9", 20 | "animejs": "^3.2.1", 21 | "autoprefixer": "^10.4.13", 22 | "contentlayer": "0.3.0", 23 | "esbuild": "0.17.5", 24 | "fs-extra": "^11.1.1", 25 | "github-slugger": "^1.4.0", 26 | "gray-matter": "^4.0.2", 27 | "image-downloader": "^4.3.0", 28 | "image-size": "1.0.0", 29 | "mdx-bundler": "^9.0.0", 30 | "next": "13.1.6", 31 | "next-contentlayer": "0.3.0", 32 | "next-themes": "^0.2.0", 33 | "pinyin": "^3.0.0-alpha.5", 34 | "pliny": "0.0.10", 35 | "postcss": "^8.4.16", 36 | "react": "18.2.0", 37 | "react-dom": "18.2.0", 38 | "reading-time": "1.5.0", 39 | "rehype-autolink-headings": "^6.1.0", 40 | "rehype-citation": "^0.5.0", 41 | "rehype-katex": "^6.0.2", 42 | "rehype-preset-minify": "6.0.0", 43 | "rehype-prism-plus": "^1.5.0", 44 | "rehype-slug": "^5.0.0", 45 | "remark": "^14.0.2", 46 | "remark-gfm": "^3.0.1", 47 | "remark-math": "^5.1.1", 48 | "socks-proxy-agent": "^7.0.0", 49 | "tailwindcss": "^3.2.2", 50 | "unist-util-visit": "^4.1.0" 51 | }, 52 | "devDependencies": { 53 | "@svgr/webpack": "^6.3.1", 54 | "@types/react": "^18.0.18", 55 | "@typescript-eslint/eslint-plugin": "^5.36.2", 56 | "@typescript-eslint/parser": "^5.36.2", 57 | "cross-env": "^7.0.3", 58 | "dedent": "^0.7.0", 59 | "esbuild-register": "3.4.2", 60 | "eslint": "^8.23.0", 61 | "eslint-config-next": "13.1.6", 62 | "eslint-config-prettier": "^8.5.0", 63 | "eslint-plugin-prettier": "^4.2.1", 64 | "file-loader": "^6.0.0", 65 | "globby": "11.0.3", 66 | "husky": "^8.0.0", 67 | "js-yaml": "4.1.0", 68 | "lint-staged": "^13.0.0", 69 | "prettier": "^2.7.0", 70 | "prettier-plugin-tailwindcss": "^0.2.2", 71 | "typescript": "^4.8.3" 72 | }, 73 | "lint-staged": { 74 | "*.+(js|jsx|ts|tsx)": [ 75 | "eslint --fix" 76 | ], 77 | "*.+(js|jsx|ts|tsx|json|css|md|mdx)": [ 78 | "prettier --write" 79 | ] 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /components/MobileNav.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react' 2 | import Link from './Link' 3 | import headerNavLinks from '@/data/headerNavLinks' 4 | 5 | const MobileNav = () => { 6 | const [navShow, setNavShow] = useState(false) 7 | 8 | const onToggleNav = () => { 9 | setNavShow((status) => { 10 | if (status) { 11 | document.body.style.overflow = 'auto' 12 | } else { 13 | // Prevent scrolling 14 | document.body.style.overflow = 'hidden' 15 | } 16 | return !status 17 | }) 18 | } 19 | 20 | return ( 21 |
22 | 40 |
45 |
46 | 64 |
65 | 78 |
79 |
80 | ) 81 | } 82 | 83 | export default MobileNav 84 | -------------------------------------------------------------------------------- /css/prism.css: -------------------------------------------------------------------------------- 1 | /** 2 | * CSS Styles for code highlighting. 3 | * Feel free to customize token styles 4 | * by copying from a prismjs compatible theme: 5 | * https://github.com/PrismJS/prism-themes 6 | */ 7 | 8 | /* Code title styles */ 9 | .remark-code-title { 10 | @apply rounded-t bg-gray-700 px-5 py-3 font-mono text-sm font-bold text-gray-200; 11 | } 12 | 13 | .remark-code-title + div > pre { 14 | @apply mt-0 rounded-t-none; 15 | } 16 | 17 | /* Code block styles */ 18 | .code-highlight { 19 | @apply float-left min-w-full; 20 | } 21 | 22 | .code-line { 23 | @apply -mx-4 block border-l-4 border-transparent pl-4 pr-4; 24 | } 25 | 26 | .code-line.inserted { 27 | @apply bg-green-500 bg-opacity-20; 28 | } 29 | 30 | .code-line.deleted { 31 | @apply bg-red-500 bg-opacity-20; 32 | } 33 | 34 | .highlight-line { 35 | @apply -mx-4 border-l-4 border-primary-500 bg-gray-700 bg-opacity-50; 36 | } 37 | 38 | .line-number::before { 39 | @apply mr-4 -ml-2 inline-block w-4 text-right text-gray-400; 40 | content: attr(line); 41 | } 42 | 43 | /* Token styles */ 44 | /** 45 | * MIT License 46 | * Copyright (c) 2018 Sarah Drasner 47 | * Sarah Drasner's[@sdras] Night Owl 48 | * Ported by Sara vieria [@SaraVieira] 49 | * Added by Souvik Mandal [@SimpleIndian] 50 | */ 51 | .token.comment, 52 | .token.prolog, 53 | .token.cdata { 54 | color: rgb(99, 119, 119); 55 | font-style: italic; 56 | } 57 | 58 | .token.punctuation { 59 | color: rgb(199, 146, 234); 60 | } 61 | 62 | .namespace { 63 | color: rgb(178, 204, 214); 64 | } 65 | 66 | .token.deleted { 67 | color: rgba(239, 83, 80, 0.56); 68 | font-style: italic; 69 | } 70 | 71 | .token.symbol, 72 | .token.property { 73 | color: rgb(128, 203, 196); 74 | } 75 | 76 | .token.tag, 77 | .token.operator, 78 | .token.keyword { 79 | color: rgb(127, 219, 202); 80 | } 81 | 82 | .token.boolean { 83 | color: rgb(255, 88, 116); 84 | } 85 | 86 | .token.number { 87 | color: rgb(247, 140, 108); 88 | } 89 | 90 | .token.constant, 91 | .token.function, 92 | .token.builtin, 93 | .token.char { 94 | color: rgb(130, 170, 255); 95 | } 96 | 97 | .token.selector, 98 | .token.doctype { 99 | color: rgb(199, 146, 234); 100 | font-style: italic; 101 | } 102 | 103 | .token.attr-name, 104 | .token.inserted { 105 | color: rgb(173, 219, 103); 106 | font-style: italic; 107 | } 108 | 109 | .token.string, 110 | .token.url, 111 | .token.entity, 112 | .language-css .token.string, 113 | .style .token.string { 114 | color: rgb(173, 219, 103); 115 | } 116 | 117 | .token.class-name, 118 | .token.atrule, 119 | .token.attr-value { 120 | color: rgb(255, 203, 139); 121 | } 122 | 123 | .token.regex, 124 | .token.important, 125 | .token.variable { 126 | color: rgb(214, 222, 235); 127 | } 128 | 129 | .token.important, 130 | .token.bold { 131 | font-weight: bold; 132 | } 133 | 134 | .token.italic { 135 | font-style: italic; 136 | } 137 | 138 | .token.table { 139 | display: inline; 140 | } 141 | 142 | .token.table { 143 | display: inline; 144 | } 145 | -------------------------------------------------------------------------------- /components/Article.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @next/next/no-img-element */ 2 | import React from 'react' 3 | import Link from '@/components/Link' 4 | import Tag from '@/components/Tag' 5 | import siteMetadata from '@/data/siteMetadata' 6 | import { formatDate } from 'pliny/utils/formatDate' 7 | import summaryJson from '@/data/summary.json' 8 | 9 | type PostFrontMatter = { 10 | title: string 11 | date: string 12 | tags: string[] 13 | lastmod?: string 14 | draft?: boolean 15 | summary?: string 16 | images?: string[] 17 | authors?: string[] 18 | layout?: string 19 | canonicalUrl?: string 20 | slug: string 21 | fileName?: string 22 | } 23 | 24 | export default function Article({ slug, date, title, summary, tags, images }: PostFrontMatter) { 25 | const src = Array.isArray(images) ? images[0] : images 26 | const newSummary = summary || (summaryJson[title] && summaryJson[title].summary) 27 | return ( 28 |
  • 29 |
    30 |
    31 |
    32 | {src ? ( 33 |
    34 | 39 | {title} 44 | 45 |
    46 | ) : null} 47 |
    发布时间
    48 |
    49 | 50 |
    51 |
    52 |
    53 |
    54 |
    55 |

    56 | 57 | {title} 58 | 59 |

    60 |
    61 | {tags.map((tag) => ( 62 | 63 | ))} 64 |
    65 |
    66 |
    {newSummary}
    67 |
    68 |
    69 | 74 | 查看更多 → 75 | 76 |
    77 |
    78 |
    79 |
    80 |
  • 81 | ) 82 | } 83 | -------------------------------------------------------------------------------- /contentlayer.config.ts: -------------------------------------------------------------------------------- 1 | import { defineDocumentType, ComputedFields, makeSource } from 'contentlayer/source-files' 2 | import readingTime from 'reading-time' 3 | import path from 'path' 4 | // Remark packages 5 | import remarkGfm from 'remark-gfm' 6 | import remarkMath from 'remark-math' 7 | import { 8 | remarkExtractFrontmatter, 9 | remarkCodeTitles, 10 | remarkImgToJsx, 11 | extractTocHeadings, 12 | } from 'pliny/mdx-plugins.js' 13 | // Rehype packages 14 | import rehypeSlug from 'rehype-slug' 15 | import rehypeAutolinkHeadings from 'rehype-autolink-headings' 16 | import rehypeKatex from 'rehype-katex' 17 | import rehypeCitation from 'rehype-citation' 18 | import rehypePrismPlus from 'rehype-prism-plus' 19 | import rehypePresetMinify from 'rehype-preset-minify' 20 | 21 | const root = process.cwd() 22 | 23 | const computedFields: ComputedFields = { 24 | readingTime: { type: 'json', resolve: (doc) => readingTime(doc.body.raw) }, 25 | slug: { 26 | type: 'string', 27 | resolve: (doc) => doc._raw.flattenedPath.replace(/^.+?(\/)/, ''), 28 | }, 29 | path: { 30 | type: 'string', 31 | resolve: (doc) => doc._raw.flattenedPath, 32 | }, 33 | filePath: { 34 | type: 'string', 35 | resolve: (doc) => doc._raw.sourceFilePath, 36 | }, 37 | toc: { type: 'string', resolve: (doc) => extractTocHeadings(doc.body.raw) }, 38 | } 39 | 40 | export const Blog = defineDocumentType(() => ({ 41 | name: 'Blog', 42 | filePathPattern: 'blog/**/*.md', 43 | contentType: 'mdx', 44 | fields: { 45 | title: { type: 'string', required: true }, 46 | date: { type: 'date', required: true }, 47 | tags: { type: 'list', of: { type: 'string' } }, 48 | lastmod: { type: 'date' }, 49 | draft: { type: 'boolean' }, 50 | summary: { type: 'string' }, 51 | images: { type: 'list', of: { type: 'string' } }, 52 | authors: { type: 'list', of: { type: 'string' } }, 53 | layout: { type: 'string' }, 54 | bibliography: { type: 'string' }, 55 | canonicalUrl: { type: 'string' }, 56 | }, 57 | computedFields, 58 | })) 59 | 60 | export const Authors = defineDocumentType(() => ({ 61 | name: 'Authors', 62 | filePathPattern: 'authors/**/*.mdx', 63 | contentType: 'mdx', 64 | fields: { 65 | name: { type: 'string', required: true }, 66 | avatar: { type: 'string' }, 67 | occupation: { type: 'string' }, 68 | company: { type: 'string' }, 69 | email: { type: 'string' }, 70 | twitter: { type: 'string' }, 71 | linkedin: { type: 'string' }, 72 | github: { type: 'string' }, 73 | layout: { type: 'string' }, 74 | }, 75 | computedFields, 76 | })) 77 | 78 | export default makeSource({ 79 | contentDirPath: 'data', 80 | documentTypes: [Blog, Authors], 81 | mdx: { 82 | cwd: process.cwd(), 83 | remarkPlugins: [ 84 | remarkExtractFrontmatter, 85 | remarkGfm, 86 | remarkCodeTitles, 87 | remarkMath, 88 | remarkImgToJsx, 89 | ], 90 | rehypePlugins: [ 91 | rehypeSlug, 92 | rehypeAutolinkHeadings, 93 | rehypeKatex, 94 | [rehypeCitation, { path: path.join(root, 'data') }], 95 | [rehypePrismPlus, { ignoreMissing: true }], 96 | rehypePresetMinify, 97 | ], 98 | }, 99 | }) 100 | -------------------------------------------------------------------------------- /data/blog/benditiaoshiweixinjssdk.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 本地调试微信jssdk 3 | date: 2020-03-20T14:43:52Z 4 | lastmod: 2020-03-20T15:15:05Z 5 | summary: 6 | tags: ["原生JS", "微信", "jssdk", "charles"] 7 | draft: false 8 | layout: PostLayout 9 | images: ['/static/images/banner/js4.jpeg'] 10 | bibliography: references-data.bib 11 | --- 12 | 13 | 因为微信jssdk的使用是需要绑定js安全域名,所以我们在普通的本地环境是无法正常调试微信jssdk的,只能上传代码到我们的测试or生产环境去进行调试;但是这样一来一去花费的时间跟精力瞬间就成倍增加长了,所以我们肯定要想办法提高我们的开发效率;主要有两个思路 14 | 15 | 思路一:配置测试环境or生产环境的域名访问本地项目 16 | 17 | 思路二:使用charles等抓包工具将测试or生产环境直接代理到本地,访问本地代码 18 | 19 | ## 配置测试环境or生产环境的域名访问本地项目 20 | 21 | 1、配置host,将测试or生产的域名指向本地的127.0.0.1 22 | 23 | ```shell 24 | 127.0.0.1 webapp.test.com 25 | ``` 26 | 27 | 然后我们直接在本地访问 webapp.test.com:本地项目的具体端口号,如果能够正常访问到本地项目则配置成功 28 | 29 | 2、如果本地项目端口不是80端口而是其它端口,则需要使用nginx等工具将80端口转发到我们本地项目的实际端口,如果是80端口则直接忽略这一步 30 | 31 | ```shell 32 | // mac 下查看某个端口是否被占用 33 | lsof -i:80 34 | 35 | // 杀掉某个进程,pid在查看进程的时候可以看到 36 | kill pid 37 | ``` 38 | 39 | 转发80端口的目的是因为jssdk配置的安全域名仅支持80(http)和443(https)两个端口 40 | 41 | ```js 42 | server { 43 | listen 80; 44 | server_name webapp.test.com; 45 | location / { 46 | proxy_pass http://127.0.0.1:5000/; 47 | } 48 | } 49 | ``` 50 | 51 | 注意修改完端口之后需要sudo nginx -s reload重启一下nginx 52 | 53 | 然后我们在本地访问直接访问webapp.test.com,如果能够访问到本地项目则端口转发成功 54 | 55 | 3、我们在pc端的浏览器是无法调试的,所以我们需要在手机上进行调试,我们直接在手机微信上访问webapp.test.com要么直接访问到了测试环境,要么因为确实路径访问不成功;因为这时候的dns解析出来的地址还是测试环境的地址,要想手机上webapp.test.com访问的是本地环境,那么我们需要借助charles等抓包工具; 56 | 57 | 让我们在手机上通过webapp.test.com可以正常的访问到本地环境之后,就可以本地调试微信jssdk了 58 | 59 | 主要手机必须跟电脑连在用一个内网上 60 | 61 | 常见问题: 62 | 63 | - 提示invalid url domain,表示当前页面所在域名与使用的appid没有绑定,去微信微信公众号后台确认是否配置了该安全域名 64 | 65 | - invalid signature签名错误,这个90%以上的场景都是获取签名参数传入的url不匹配,这里需要根据ios与安卓、小程序webview等不同的环境去进行区分 66 | 67 | - permission denied 表示当前的jssdk方法没有权限使用,需要去微信微信公众号后台检查权限是否开启 68 | 69 | - ios中直接访问本地配置的webapp.test.com域名可能出现一致访问的是测试环境的https:webapp.test.com的情况,原因是dns缓存,但是目前使用几种ios上清除dsn缓存的方法,入开启飞行模式然后在关闭,手动修改无线网络链接中的dns解析地址,还原网络设置都没有清除dns缓存,这个后续抽时间在试下,因为目前微信7.0版本之后安卓是不能抓包小程序了,所以我们需要用ios来抓包调试小程序webview 70 | 71 | - chrome浏览器上访问webapp.test.com时chrome强制转换成了https:webapp.test.com,这是需要进入chrome://net-internals/#hsts 72 | 73 | 输入webapp.test.com域名,如果有值则会进行强制转换 74 | 75 | ![image](https://user-images.githubusercontent.com/20950813/77176677-61e7b000-6aff-11ea-9174-4619d51aea3b.png) 76 | 77 | 需要在下面这个位置输入域名删除,这样我们就可以通过http访问该站点了 78 | 79 | ![image](https://user-images.githubusercontent.com/20950813/77176572-4086c400-6aff-11ea-8785-dfa2a037a685.png) 80 | 81 | 补充host的一点知识: 82 | 83 | hosts —— the static table lookup for host name(主机名查询静态表) 84 | 85 | hosts文件是一个用于储存计算机网络中各节点信息的计算机文件。这个文件负责将主机名映射到相应的IP地址。hosts文件通常用于补充或取代网络中DNS的功能。和DNS不同的是,计算机的用户可以直接对hosts文件进行控制。 86 | 87 | Hosts是一个没有扩展名的系统文件,其作用就是将一些常用的网址域名与其对应的IP地址建立一个关联“数据库”,当用户在浏览器中输入一个需要登录的网址时,系统会首先自动从Hosts文件中寻找对应的IP地址,一旦找到,系统会立即打开对应网页,如果没有找到,则系统会再将网址提交DNS域名解析服务器进行IP地址的解析。 88 | 89 | 优先级 : dns缓存 > hosts > dns服务 90 | 91 | hosts在各个系统中所在的文件夹: 92 | 93 | Windows 系统hosts位于 C:\Windows\System32\drivers\etc\hostsAndroid 94 | Mac(苹果电脑)系统hosts位于 /etc/hosts 95 | 96 | ## 使用charles等抓包工具将测试or生产环境的代码代理到本地 97 | 98 | 可以参考[移动端抓包及调试](https://github.com/willson-wang/Blog/issues/35) 99 | 100 | ## 参考链接 101 | https://osxdaily.com/2015/03/31/clear-dns-cache-ios/ 102 | https://www.jianshu.com/p/302571f2dae0 103 | https://juejin.im/post/5c71f8eef265da2db5423bc3 104 | 105 | -------------------------------------------------------------------------------- /data/blog/hanshujieliu.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 函数节流 3 | date: 2018-03-01T08:44:29Z 4 | lastmod: 2018-03-01T08:44:35Z 5 | summary: 6 | tags: ["原生JS", "节流"] 7 | draft: false 8 | layout: PostLayout 9 | bibliography: references-data.bib 10 | --- 11 | 12 | 关于节流的实现,有两种主流的实现方式,一种是使用时间戳,一种是设置定时器,第一种事件会立刻执行,第二种事件会在 n 秒后第一次执行,第一种事件停止触发后没有办法再执行事件,第二种事件停止触发后依然会再执行一次事件 13 | 14 | ``` 15 |
    16 | 17 | var box = document.getElementsByClassName("box")[0]; 18 | var WAIT_TIME = 500; 19 | 20 | var throttle1 = function (func, time){ 21 | var previous = 0, 22 | _this, 23 | args; 24 | return function (){ 25 | var now = +new Date(); //使用时间搓 26 | _this = this; 27 | args = arguments; 28 | 29 | if(now - previous > time){ 30 | func.apply(_this, args); 31 | previous = now; 32 | } 33 | } 34 | } 35 | 36 | var throttle2 = function (func, time){ 37 | var _this, 38 | timeout, 39 | args; 40 | return function (){ 41 | _this = this; 42 | args = arguments; 43 | if(!timeout){ 44 | timeout = setTimeout(function (){ 45 | func.apply(_this, args); 46 | timeout = null; 47 | }, time); 48 | } 49 | 50 | } 51 | } 52 | 53 | var throttle3 = function (func, time){//时间戳与定时器的结合 54 | var previous = 0, 55 | _this, 56 | timeout, 57 | args; 58 | 59 | var later = function (){ 60 | previous = +new Date(); 61 | timeout = null; 62 | func.apply(_this, args); 63 | } 64 | 65 | return function (){ 66 | var now = +new Date(); 67 | var remaining = time - (now - previous); //下次执行func剩余的时间 68 | 69 | _this = this; 70 | args = arguments; 71 | 72 | if(remaining < 0 || remaining > time){ 73 | if(timeout){ 74 | clearTimeout(timeout); 75 | timeout = null; 76 | } 77 | previous = now; 78 | func.apply(_this, args); 79 | }else if(!timeout) { 80 | timeout = setTimeout(later, remaining); 81 | } 82 | } 83 | 84 | } 85 | 86 | var throttle4 = function (func, time, options){//时间戳与定时器的结合 87 | var previous = 0, 88 | _this, 89 | timeout, 90 | args; 91 | 92 | if (!options) options = {}; 93 | 94 | var later = function (){ 95 | previous = options.leading === false ? 0 : new Date().getTime(); 96 | timeout = null; 97 | func.apply(_this, args); 98 | if (!timeout) _this = args = null; 99 | } 100 | 101 | return function (){ 102 | var now = +new Date(); 103 | if(!previous && options.leading === false) previous = now; 104 | var remaining = time - (now - previous); //下次执行func剩余的时间 105 | 106 | _this = this; 107 | args = arguments; 108 | 109 | if(remaining < 0 || remaining > time){ 110 | if(timeout){ 111 | clearTimeout(timeout); 112 | timeout = null; 113 | } 114 | previous = now; 115 | func.apply(_this, args); 116 | if (!timeout) _this = args = null; 117 | }else if(!timeout && options.trailing !== false) { 118 | timeout = setTimeout(later, remaining); 119 | } 120 | } 121 | 122 | } 123 | 124 | var getUserAction = function (){ 125 | console.log(this); 126 | console.log(arguments); 127 | } 128 | 129 | // box.onmousemove = throttle1(getUserAction, 1000); 130 | // box.onmousemove = throttle2(getUserAction, 1000); 131 | // box.onmousemove = throttle3(getUserAction, 1000); 132 | // box.onmousemove = throttle4(getUserAction, 1000,{ 133 | // leading:false 表示禁用第一次执行 trailing: false 表示禁用停止触发的回调 134 | // }); 135 | 136 | box.onmousemove = throttle4(getUserAction, 1000,{ 137 | trailing: false 138 | }); 139 | ``` 140 | 141 | -------------------------------------------------------------------------------- /data/blog/flexbujujiyingyong.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: flex布局及应用 3 | date: 2018-03-01T07:16:33Z 4 | lastmod: 2018-03-01T07:18:19Z 5 | summary: 6 | tags: ["CSS", "flex"] 7 | draft: false 8 | layout: PostLayout 9 | bibliography: references-data.bib 10 | --- 11 | 12 | ### 简介 13 | flex布局简称弹性盒模型布局,是2009年w3c提出的一种可以简洁、快速弹性布局的属性。主要思想是给予容器控制内部元素高度和宽度的能力,是目前移动端布局常用的一种方式 14 | 15 | flex布局主要包括flex容器与flex项目,而flex项目又可以是一个新的flex容器,依次类推;只要给一个元素设置了display: flex;那么浏览器在渲染的时候就会该元素为一个flex容器,其内的子元素为flex项目; 16 | 17 | flex中的有两条轴分别代表水平和垂直方向,常常称为主轴与交叉轴,默认情况下主轴为水平方向(从左至右),交叉轴为垂直方向,因为flex-direction的默认值为row 18 | 19 | ![flexbox](https://user-images.githubusercontent.com/20950813/36831658-6f302d96-1d63-11e8-93fa-f6f4f059bb89.png) 20 | 21 | 22 | ### flex容器的属性 23 | 24 | 1. flex-direction: row(行,左至右)(默认值) || column(列,上至下) || row-reverse(行,右至左) || column-reverse(列, 下至上); 控制Flex项目沿着主轴(Main Axis)的排列方向 25 | 26 | 2. flex-wrap: wrap(当主轴方向无法显示足够的flex项目时,则会换行显示,从主轴的默认排列方向)(默认值) || nowrap(不换行显示,所有的flex项目显示在这一行,当flex容器的宽度超过视窗宽度则出现滚动条) || wrap-reverse(当主轴方向无法显示足够的flex项目时,则会换行显示,从主轴的默认排列方向的反方向开始排列); 27 | 28 | 3. flex-flow: flex-direction flex-wrap 是这两个属性的简写属性 29 | 30 | 4. justify-content(类似text-align属性)(只对主轴有效): flex-start(让flex项目从主轴默认开始的方向对齐)(默认值) || flex-end(让flex项目从主轴默认结束的方向对齐) || center(延主轴居中对齐) || space-between(让除了第一个和最一个Flex项目的两者间间距相同(两端对齐)) || space-around(让每个Flex项目具有相同的空间,即让每个flex项目的左右有一个相同的margin值) 31 | 32 | 5. align-items(类似text-align属性)(只对交叉轴有效): flex-start(让flex项目从交叉轴默认开始的方向对齐) || flex-end(让flex项目从交叉轴默认结束的方向对齐)(默认值) || center(延交叉轴居中对齐) || stretch(让所有的flex项目与flex容器等高 or 等宽根据主轴的方向来,如果是row方向则等高,column方向则等宽)(默认值) || baseline(延基线对齐) 33 | 34 | 6. align-content(与align-items类似只是少了baseline属性值,也是设置flex项目的对齐方式): flex-start(让多行flex项目从交叉轴默认开始的方向对齐) || flex-end(让多行flex项目从交叉轴默认结束的方向对齐)(默认值) || center(延交叉轴居中对齐) || stretch(延交叉轴的方向拉伸flex的项目,让flex项目占满flex容器一个合适的高度or宽度)(默认值) 35 | 36 | ### flex-item项目属性 37 | 38 | 1. order(定义flex项目在主轴方向的排列顺序):number(所有的flex项目order默认值为0,值越大排列越靠后,值越小排列越靠前,允许正负值); 39 | 40 | 2. flex-grow(控制Flex项目在容器有多余的空间如何放大(扩展)): 0(默认值为0) or 正值,0表示Flex项目不会增长,填充Flex容器可用空间,正值表示flex项目会随着flex容器变大 41 | 42 | 3. flex-shrink(控制Flex项目在没有额外空间如何缩小): 0 or正值(默认值为1),0表示Flex项目不会变小,填充Flex容器可用空间,正值表示flex项目会随着flex容器缩小 43 | 44 | 4. flex-basis(指定Flex项目的初始大小):% || em || rem || px (默认值为auto);注意的是flex-basis: 0px不能写成flex-basis:0; 45 | 46 | 5. flex(简写属性): flex-grow flex-shrink flex-basis; flex: 0 1 auto;(默认属性值) 47 | 48 | 7. align-self(改变flex项目沿着侧轴的位置,而不影响相邻的弹性项目):auto(继承父元素的align-items的值) || flex-start || flex-end || center || baseline || stretch(默认值);当不想局限于flex容器align-items对齐方式时就可以使用align-self属性来自动设置 49 | 50 | 绝对flex项目与相对flex项目:一个相对Flex项目内的间距是根据它的内容大小来计算的。而在绝对Flex项目中,只根据 flex 属性来计算,而不是内容; flex: auto; or flex: 1 1 auto;的flex项目为相对flex项目; flex:1; or flex: 1 1 0;的项目为绝对flex项目 51 | 52 | ### 应用 53 | 54 | 1. flex固定头部or底部 55 | ``` 56 | html,body { 57 | height: 100%; 58 | width: 100%; 59 | margin: 0; 60 | padding: 0; 61 | } 62 | .app { 63 | display: flex; 64 | height: 100%; 65 | flex-direction: column; 66 | } 67 | .header { 68 | flex: 0 0 50px; 69 | background: red; 70 | } 71 | .main { 72 | flex: 1; 73 | background-color: blue; 74 | overflow: auto; 75 | } 76 | .content { 77 | height: 100vh; 78 | } 79 | 80 |
    81 |
    header
    82 |
    83 |
    84 | content 85 |
    86 |
    87 |
    88 | ``` 89 | 90 | ### 移动端兼容性 91 | ![image](https://user-images.githubusercontent.com/20950813/36831579-3c5ba44a-1d63-11e8-86b0-b52703daaea4.png) 92 | 93 | 参考链接:https://developer.mozilla.org/zh-CN/docs/Web/CSS/CSS_Flexible_Box_Layout/Using_CSS_flexible_boxes 94 | -------------------------------------------------------------------------------- /data/blog/zhengzebiaodashidexianhangduanyan(lookahead)hehouhangduanyan(lookbehind).md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 正则表达式的先行断言(lookahead)和后行断言(lookbehind) 3 | date: 2019-04-23T12:44:05Z 4 | lastmod: 2023-03-26T09:38:37Z 5 | summary: 6 | tags: ["原生JS", "先行断言", "后行断言", "js正则"] 7 | draft: false 8 | layout: PostLayout 9 | bibliography: references-data.bib 10 | --- 11 | 12 | ## 关于正则的先行断言(lookahead)和后行断言(lookbehind)总共分为4种,如下所示 13 | 1. 零宽正向先行断言`p(?=pattern)` 14 | 2. 零宽负向先行断言`p(?!pattern)` 15 | 3. 零宽正向后行断言`(?<=pattern)p` 16 | 4. 零宽负向后行断言`(?'.replace( 75 | /(?<=(" 79 | ``` 80 | 81 | ## 参考链接 82 | http://es6.ruanyifeng.com/#docs/regex#%E5%90%8E%E8%A1%8C%E6%96%AD%E8%A8%80 83 | https://blog.51cto.com/cnn237111/749047 84 | 85 | -------------------------------------------------------------------------------- /data/blog/liulanqineideshijianxunhuanjizhijimacrotaskyumicrotask.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 浏览器内的事件循环机制及macrotask与microtask 3 | date: 2017-12-26T11:17:29Z 4 | lastmod: 2018-02-28T03:11:13Z 5 | summary: 6 | tags: ["浏览器", "macrotask", "microtask"] 7 | draft: false 8 | layout: PostLayout 9 | bibliography: references-data.bib 10 | --- 11 | 12 | 1. **浏览器的主要组件**包括用户界面、浏览器引擎、呈现引擎(即解析html及css的引擎)、网络、用户界面后端、JavaScript 解释器(js引擎如chrome的v8)、数据存储 13 | 14 | 2. **清楚浏览器与javascript的关系** 15 | 宿主关系,即javascript代码的执行依赖于浏览器,因为浏览器内置了javascript解析器(js引擎,如chrome v8) 16 | 17 | 3. **event loop到底属于谁的运行机制** 18 | 属于浏览器的一套基于事件驱动的循环检测机制,而不是属于js的循环检测机制 19 | 根据规范每个浏览器至少要有一个事件循环机制 20 | 一个eventloop有一个or多个task queues,一个task queues是一系列有序的task集合 21 | task主要包括Events(并非所有事件都使用任务队列调度,许多任务在其他任务中调度。)、Parsing(解析html)、Callbacks、Using a resource(获取资源)、Reacting to DOM manipulation(dom操作如元素插入文档时) 22 | 23 | 24 | 4. **js是单线程的缘故** 25 | 因为js的主要目的是用于用户交互,而同时存在多个线程的话,可能会造成干扰,影响交互 26 | 27 | 5. **什么是同步,什么是异步,什么是同步操作(同步任务),什么是异步操作(异步任务)** 28 | 一般而言,操作分为:发出调用和得到结果两步。发出调用,立即得到结果是为同步。发出调用,但无法立即得到结果,需要额外的操作才能得到预期的结果是为异步。 29 | 同步:就是调用之后一直等待,直到返回结果。 30 | 异步:异步则是调用之后,不能直接拿到结果,通过一系列的手段才最终拿到结果(调用之后,拿到结果中间的时间可以介入其他任务) 31 | 同步操作: 就是执行同步的过程;直接返回一个值的函数 32 | 异步操作: 就是执行异步的过程;如ajax,定时器 33 | 34 | 6. **什么是主线程,什么是任务队列,eventloop** 35 | 主线程是浏览器的一个设定,是浏览器的一个运行池,是浏览器的的运行机制,而不是js的运行机制,是所有任务都在上面执行的线程 36 | 任务队列:是一系列包含各种事件,异步操作,定时器等各种队列的一个集合,是在主线程上的一切调用,而队列是任务的集合 37 | 任务:分为macrotask(setTimeout、setInterval、setImmediate、I/O、UI交互事件),microtask(Promise、process.nextTick、MutaionObserver)而主线程与任务队列之间又通过一个eventloop来保证主线程最大程度上来执行js代码(主线程永远在执行中。主线程会不断检查任务队列,即进行某个操作时,会产生某个事件,同时也会设置一个watcher,事件循环的过程中从该watcher上处理事件,处理完已有的事件后,处理下一个watcher,检查完所有watcher后,进入下一轮检查,对某类事件不关心时,则没有相关watcher),避免出现阻塞等现象,即主线程上的代码执行完毕之后,就会去拿任务队列里面的任务来放到主线程执行,依此循环往复,同时任务队列之间是可以插队的,如定时器任务, 38 | 39 | 40 | 7. **javascript是一门事件驱动的脚本语言**,而事件驱动就是将一切抽象为事件。IO操作完成是一个事件,用户点击一次鼠标是事件,Ajax完成了是一个事件,一个图片加载完成是一个事件 41 | 42 | 8. **定时器**是浏览器有一个专门的队列来存放定时器,并且有一个专门的机制来判断定时器插入任务队列的时机,即到达时间点后,会形成一个事件(timeout事件)。不同的是一般事件是靠底层系统或者线程池之类的产生事件,定时器事件是靠事件循环不停检查系统时间来判定是否到达时间点来产生事件 43 | 换个说法当我们进行定时器调用时,首先会设置一个定时器watcher。事件循环的过程中,会去调用该watcher,检查它的事件队列上是否产生事件(比对时间的方式) 44 | 45 | 9. **DOM,AJAX,setTimeout是浏览器提供的api,而不是javascript提供的api** 46 | 47 | 10. **发起ajax前的逻辑和ajax的callback的逻辑**,是2个任务(事件),所以不存在一个事件状态这种东西,ajax回来以后浏览器只是简单的往事件队列里丢一个任务而已,之前发起ajax的那个任务早就结束消失了 48 | 49 | 11. **非堵塞**就是 js 可以异步执行,即有异步任务时,不会影响到异步任务之后的代码执行 50 | 51 | 12. **js代码的执行过程** 52 | 执行最旧的task(一次) -> 检查是否存在microtask,然后不停执行,直到清空队列(多次)-> render 重复之前步骤 53 | 54 | ``` 55 | console.log(1) 56 | 57 | setTimeout(() => { 58 | console.log(2) 59 | new Promise(resolve => { 60 | console.log(4) 61 | resolve() 62 | }).then(() => { 63 | console.log(5) 64 | }) 65 | }) 66 | 67 | new Promise(resolve => { 68 | console.log(7) 69 | resolve() 70 | }).then(() => { 71 | console.log(8) 72 | }) 73 | 74 | setTimeout(() => { 75 | console.log(9) 76 | new Promise(resolve => { 77 | console.log(11) 78 | resolve() 79 | }).then(() => { 80 | console.log(12) 81 | }) 82 | }) 83 | //1、7、8、2、4、5、9、11、12 84 | ``` 85 | 86 | **代码执行的过程为** 87 | 同步任务的代码输出为:1、7 88 | 执行microtask,直到misrotask队列为空:8 89 | 执行第一任务内的同步任务输出为:2、4 90 | 执行第一个任务内的microtask,直到misrotask队列为空: 5 91 | 执行第二任务内的同步任务输出为:9、11 92 | 执行第二个任务内的microtask,直到misrotask队列为空: 12 93 | 依此类推 94 | 95 | eventloop已经涉及到底层的一些原理,因为眼界有限,总结会有点片面,不过可以记录下来,等之后有了更深的理解之后再回头来看。 96 | 97 | 参考链接 98 | https://html.spec.whatwg.org/multipage/webappapis.html#event-loops 99 | https://developer.mozilla.org/zh-CN/docs/Web/API/Window/setTimeout 100 | http://blog.csdn.net/lin_credible/article/details/40143961 101 | https://juejin.im/post/5a6155126fb9a01cb64edb45 102 | 103 | -------------------------------------------------------------------------------- /data/blog/jiyuaxiosdexhrfengzhuang.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 基于axios的xhr封装 3 | date: 2018-07-10T11:29:33Z 4 | lastmod: 2018-07-10T11:29:44Z 5 | summary: 6 | tags: ["原生JS", "axios", "xhr"] 7 | draft: false 8 | layout: PostLayout 9 | bibliography: references-data.bib 10 | --- 11 | 12 | ### 常规封装 13 | 14 | ``` 15 | const http = axios.create({ 16 | baseURL: 'https://some-domain.com/api/', 17 | timeout: 1000, 18 | headers: {'X-Custom-Header': 'foobar'} 19 | }) 20 | 21 | http.interceptors.request.use((config) => { 22 | return config; 23 | }, (error) => { 24 | return Promise.reject(error) 25 | }) 26 | 27 | http.interceptors.response.use((response) => { 28 | if (response.code === 0) { 29 | return response.data; 30 | } 31 | 32 | return Promise.reject({ 33 | msg: response.errMsg, 34 | code: response.code 35 | }) 36 | }, (err) => { 37 | return Promise.reject(err) 38 | }) 39 | 40 | 41 | function get(url, opts = {params: {}}) { 42 | return new Promise((resolve, reject) => { 43 | // 这里直接做一些对url及传入params的处理 44 | http.get(url, opts).then((res) => { 45 | resolve(res); 46 | }).catch((err) => { 47 | // 这里可以统一处理提示错误的方式 48 | reject(err); 49 | }) 50 | }) 51 | } 52 | 53 | function post(url, data, opts) { 54 | return new Promise((resolve, reject) => { 55 | http.post(url, data, opts).then((res) => { 56 | resolve(res); 57 | }).catch((err) => { 58 | reject(err); 59 | }) 60 | }) 61 | } 62 | ``` 63 | 64 | ### 对参数及url处理及返回值处理的封装 65 | 66 | ``` 67 | let $httpResolve; 68 | let apiDomain = ''; 69 | 70 | let $httpPromise = new Promise((resolve) => { 71 | $httpResolve = resolve; 72 | }) 73 | 74 | 75 | if (axios) { 76 | // 这里可以换成任何基于axios封装的方法 77 | $httpResolve(axios); 78 | } 79 | 80 | function makeHttp(method, arg) { 81 | return $httpPromise 82 | .then(($http) => { 83 | return $http[method](...arg); 84 | }) 85 | .then((res) => { 86 | if (res.data.retCode === '-1500004') { 87 | throw new Error('缺少参数'); 88 | } 89 | return res; 90 | }) 91 | } 92 | 93 | function get(...args) { 94 | const [url, ...rest] = args; 95 | return $httpPromise.then(() => { 96 | return makeHttp('get', [addBaseUrl(addBaseParam(url)), ...rest]); 97 | }) 98 | } 99 | 100 | function post(...args) { 101 | const [url, ...rest] = args; 102 | return $httpPromise.then(() => { 103 | return makeHttp('post', [addBaseUrl(addBaseParam(url)), ...rest]); 104 | }) 105 | } 106 | 107 | 108 | function request(...args) { 109 | const [url, ...rest] = args; 110 | return $httpPromise.then(() => { 111 | return makeHttp('request', [addBaseUrl(addBaseParam(url)), ...rest]); 112 | }) 113 | } 114 | 115 | // 利用async函数,对返回值进行处理 116 | const wrap = (fn) => { 117 | return async (...args) => { 118 | const res = await fn(...args); 119 | 120 | const { retCode, errMsg, data} = res.data; 121 | 122 | if (retCode === 0) { 123 | return data; 124 | } 125 | 126 | throw new BuildInHttpError(errMsg, retCode, res); 127 | } 128 | } 129 | 130 | function BuildInHttpError(message, retCode, data) { 131 | this.message = message; 132 | this.data = data; 133 | this.retCode = retCode; 134 | } 135 | 136 | function addBaseParam(url) { 137 | if(/^\/api\//.test(url)) { 138 | const param = { 139 | token: 'afsad66332', 140 | orgCode: 'kklhjsk' 141 | } 142 | return `${url}${url.indexOf('?') > 0 ? '&' : '?'}${Qs.stringify(param)}` 143 | } 144 | return url; 145 | } 146 | 147 | function addBaseUrl(url) { 148 | if(/^\/api\//.test(url)) { 149 | return `${apiDomain}${url}` 150 | } 151 | return url; 152 | } 153 | 154 | export default { 155 | get, 156 | post, 157 | request, 158 | apiGet: wrap(get), 159 | apiPost: wrap(post), 160 | apiRequest: wrap(request) 161 | } 162 | 163 | ``` 164 | -------------------------------------------------------------------------------- /data/blog/shuzuquzhong-shuzuquzhongbingxunzhaozuidaxiang-shuzupaixu.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 数组去重、数组去重并寻找最大项、数组排序 3 | date: 2018-03-01T09:16:22Z 4 | lastmod: 2018-03-01T09:16:32Z 5 | summary: 6 | tags: ["原生JS"] 7 | draft: false 8 | layout: PostLayout 9 | bibliography: references-data.bib 10 | --- 11 | 12 | ### 简介 13 | 数组去重的方法很多,于是把平常自己用到的总结了一下 14 | 15 | ### indexOf 去重 16 | 17 | ``` 18 | var fn1 = function (arr){ 19 | var arr1 = []; 20 | for(var i=0; i max){ 119 | maxItem.length = 0; 120 | max = retObj[typeEle]; 121 | maxItem.push(arr[i]); 122 | } 123 | } 124 | 125 | return { 126 | maxItem: maxItem, //重复项最多的项 127 | ret: ret, //去重后的数组 128 | max: max, //重复的最大次数 129 | retObj: retObj //无重复项的对象 130 | } 131 | } 132 | ``` 133 | 134 | ### sort排序 135 | 136 | 使用数组的sort方法排序,当有字母的时候不建议用 137 | ``` 138 | var fn8 = function (arr){ 139 | arr.sort(function (x,y){ 140 | // return x-y //小到大 141 | return y-x //大到小 142 | }); 143 | return arr; 144 | } 145 | ``` 146 | 147 | ### 冒泡排序 148 | 149 | ``` 150 | var fn9 = function (arr){ 151 | var temp; 152 | for(var i=0; iarr[j+1]){//从小到大 155 | temp = arr[j]; 156 | arr[j] = arr[j+1]; 157 | arr[j+1] = temp; 158 | } 159 | } 160 | } 161 | return arr; 162 | } 163 | ``` 164 | 165 | ### 比较排序 166 | 167 | ``` 168 | var fn10 = function (arr){ 169 | var max, 170 | k; 171 | for(var i=0; imax){ 176 | max = arr[j]; 177 | k = j; 178 | } 179 | } 180 | arr[k] = arr[i]; 181 | arr[i] = max; 182 | } 183 | return arr; 184 | } 185 | ``` 186 | 187 | 188 | -------------------------------------------------------------------------------- /data/siteMetadata.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | /** @type {import("pliny/config").PlinyConfig } */ 4 | const siteMetadata = { 5 | title: '往事随风', 6 | author: 'willson-wang', 7 | headerTitle: 'Blog', 8 | description: '枯藤老树昏鸦,小桥流水人家,古道西风瘦马,撸码人在天涯', 9 | language: 'zh-cn', 10 | theme: 'system', // system, dark or light 11 | siteUrl: 'https://blog.willson-wang.com', 12 | siteRepo: 'https://github.com/willson-wang/Blog', 13 | siteLogo: '/static/images/logo1.png', 14 | image: '/static/images/avatar.png', 15 | socialBanner: '/static/images/twitter-card.png', 16 | email: 'wangkangsen168@163.com', 17 | github: 'https://github.com', 18 | // twitter: 'https://twitter.com/Twitter', 19 | // facebook: 'https://facebook.com', 20 | // youtube: 'https://youtube.com', 21 | // linkedin: 'https://www.linkedin.com', 22 | locale: 'zh-cn', 23 | analytics: { 24 | // If you want to use an analytics provider you have to add it to the 25 | // content security policy in the `next.config.js` file. 26 | // supports plausible, simpleAnalytics, umami or googleAnalytics 27 | plausibleDataDomain: '', // e.g. tailwind-nextjs-starter-blog.vercel.app 28 | simpleAnalytics: false, // true or false 29 | umamiWebsiteId: '', // e.g. 123e4567-e89b-12d3-a456-426614174000 30 | posthogProjectApiKey: '', // e.g. AhnJK8392ndPOav87as450xd 31 | googleAnalyticsId: process.env.NEXT_PUBLIC_GOOGLE_ANALYTICS, // e.g. UA-000000-2 or G-XXXXXXX 32 | }, 33 | newsletter: { 34 | // supports mailchimp, buttondown, convertkit, klaviyo, revue, emailoctopus 35 | // Please add your .env file and modify it according to your selection 36 | provider: 'buttondown', 37 | }, 38 | comments: { 39 | // If you want to use an analytics provider you have to add it to the 40 | // content security policy in the `next.config.js` file. 41 | // Select a provider and use the environment variables associated to it 42 | // https://vercel.com/docs/environment-variables 43 | provider: 'giscus', // supported providers: giscus, utterances, disqus 44 | giscusConfig: { 45 | // Visit the link below, and follow the steps in the 'configuration' section 46 | // https://giscus.app/ 47 | repo: process.env.NEXT_PUBLIC_GISCUS_REPO, 48 | repositoryId: process.env.NEXT_PUBLIC_GISCUS_REPOSITORY_ID, 49 | category: process.env.NEXT_PUBLIC_GISCUS_CATEGORY, 50 | categoryId: process.env.NEXT_PUBLIC_GISCUS_CATEGORY_ID, 51 | mapping: 'pathname', // supported options: pathname, url, title 52 | reactions: '1', // Emoji reactions: 1 = enable / 0 = disable 53 | // Send discussion metadata periodically to the parent window: 1 = enable / 0 = disable 54 | metadata: '0', 55 | // theme example: light, dark, dark_dimmed, dark_high_contrast 56 | // transparent_dark, preferred_color_scheme, custom 57 | theme: 'light', 58 | // theme when dark mode 59 | darkTheme: 'transparent_dark', 60 | // If the theme option above is set to 'custom` 61 | // please provide a link below to your custom theme css file. 62 | // example: https://giscus.app/themes/custom_example.css 63 | themeURL: '', 64 | // This corresponds to the `data-lang="en"` in giscus's configurations 65 | lang: 'en', 66 | }, 67 | }, 68 | // search: { 69 | // provider: 'kbar', // kbar or algolia 70 | // kbarConfig: { 71 | // searchDocumentsPath: 'search.json', // path to load documents to search 72 | // }, 73 | // provider: 'algolia', 74 | // algoliaConfig: { 75 | // // The application ID provided by Algolia 76 | // appId: 'R2IYF7ETH7', 77 | // // Public API key: it is safe to commit it 78 | // apiKey: '599cec31baffa4868cae4e79f180729b', 79 | // indexName: 'docsearch', 80 | // }, 81 | // }, 82 | } 83 | 84 | module.exports = siteMetadata 85 | -------------------------------------------------------------------------------- /data/blog/guanchazhemoshiyufabudingyuemoshidequbie.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 观察者模式与发布订阅模式的区别 3 | date: 2020-06-15T08:16:27Z 4 | lastmod: 2020-06-15T08:22:38Z 5 | summary: 6 | tags: ["原生JS", "设计模式", "观察者模式", "发布订阅模式"] 7 | draft: false 8 | layout: PostLayout 9 | images: ['/static/images/banner/js3.png'] 10 | bibliography: references-data.bib 11 | --- 12 | 13 | ## 发布订阅模式 14 | 15 | 三个核心因素:订阅者、发布者、调度中心 16 | 17 | ``` 18 | // 创建调度中心 19 | class Event { 20 | constructor() { 21 | this.subs = {} 22 | } 23 | 24 | // 提供订阅方法 25 | on(name, cb) { 26 | if (!this.subs[name]) { 27 | this.subs[name] = [cb] 28 | } else { 29 | this.subs[name].push(cb) 30 | } 31 | } 32 | 33 | // 发布方法 34 | fire(name, ...args) { 35 | if (this.subs[name]) { 36 | this.subs[name].forEach((cb) => { 37 | cb && cb(...args) 38 | }) 39 | } 40 | } 41 | 42 | // 删除订阅cb 43 | remove(name, cb) { 44 | if (this.subs[name]) { 45 | const index = this.subs[name].indexOf(cb) 46 | this.subs[name].splice(index, 1) 47 | } 48 | } 49 | 50 | // 删除所有订阅cb 51 | removeAll(name) { 52 | this.subs[name].length = 0 53 | } 54 | } 55 | 56 | const eve = new Event() 57 | 58 | eve.on('event1', function (...args) { 59 | console.log('我在监听event1 cb1', ...args) 60 | }) 61 | 62 | eve.on('event1', function (...args) { 63 | console.log('我在监听event1 cb2', ...args) 64 | }) 65 | 66 | eve.on('event1', function (...args) { 67 | console.log('我在监听event1 cb3', ...args) 68 | }) 69 | 70 | eve.removeAll('event1') 71 | 72 | eve.fire('event1', { 73 | data: { 74 | age: 1, 75 | name: 'xiaoming' 76 | } 77 | }) 78 | 79 | eve.on('event2', function (...args) { 80 | console.log('我在监听event2 cb1', ...args) 81 | }) 82 | 83 | const cb2 = function (...args) { 84 | console.log('我在监听event2 cb2', ...args) 85 | } 86 | 87 | eve.on('event2', cb2) 88 | 89 | eve.remove('event2', cb2) 90 | 91 | eve.fire('event2', { 92 | data: { 93 | age: 18, 94 | name: 'xiaohei' 95 | } 96 | }) 97 | ``` 98 | 99 | 还可以考虑不先进行监听也可以接受到消息的场景 100 | 101 | ## 观察者模式 102 | 103 | 两个核心因素:观察者、观察目标 104 | 105 | ``` 106 | // 定义观察目标 107 | class Dep { 108 | constructor() { 109 | // 为观察目标收集观察者 110 | this.subs = [] 111 | } 112 | 113 | add(watcher) { 114 | this.subs.push(watcher) 115 | } 116 | 117 | notify() { 118 | for (let i = 0; i < this.subs.length; i++) { 119 | this.subs[i].update && this.subs[i].update() 120 | } 121 | } 122 | 123 | remove(watcher) { 124 | const index = this.subs.indexOf(watcher) 125 | this.subs.splice(index, 1) 126 | } 127 | } 128 | 129 | // 定义观察者 130 | class Watch { 131 | constructor(name) { 132 | this.name = name 133 | } 134 | 135 | update() { 136 | console.log('update', this.name) 137 | } 138 | } 139 | 140 | const dep1 = new Dep() 141 | const dep2 = new Dep() 142 | 143 | const watcher1 = new Watch('watcher1') 144 | 145 | const watcher2 = new Watch('watcher2') 146 | 147 | const watcher3 = new Watch('watcher3') 148 | 149 | dep1.add(watcher1) 150 | 151 | dep2.add(watcher1) 152 | dep2.add(watcher2) 153 | dep2.add(watcher3) 154 | 155 | dep2.remove(watcher2) 156 | 157 | dep1.notify() 158 | 159 | dep2.notify() 160 | 161 | ``` 162 | 考虑对观察者去重 163 | 164 | ## 结论 165 | 166 | 发布-订阅模式是面向调度中心编程的,而观察者模式则是面向目标和观察者编程的。前者用于解耦发布者和订阅者,后者用于耦合目标和观察者,所以我们需要根据实际的业务来进行选择具体的实现方式 167 | 168 | ## 使用场景 169 | 170 | 发布订阅模式一般用于跨组件间通信; 171 | 观察者模式在vue2版本内实现依赖收集与视图更新 172 | 173 | 参考链接: 174 | [第 23 题:介绍下观察者模式和订阅-发布模式的区别,各自适用于什么场景](https://github.com/Advanced-Frontend/Daily-Interview-Question/issues/25) 175 | -------------------------------------------------------------------------------- /layouts/PostSimple.tsx: -------------------------------------------------------------------------------- 1 | import { useState, ReactNode } from 'react' 2 | import { Comments } from 'pliny/comments' 3 | import { formatDate } from 'pliny/utils/formatDate' 4 | import { CoreContent } from 'pliny/utils/contentlayer' 5 | import type { Blog } from 'contentlayer/generated' 6 | import Link from '@/components/Link' 7 | import PageTitle from '@/components/PageTitle' 8 | import SectionContainer from '@/components/SectionContainer' 9 | import { BlogSEO } from '@/components/SEO' 10 | import siteMetadata from '@/data/siteMetadata' 11 | import ScrollTopAndComment from '@/components/ScrollTopAndComment' 12 | import Toc from '@/components/toc' 13 | 14 | interface LayoutProps { 15 | content: CoreContent 16 | children: ReactNode 17 | next?: { path: string; title: string } 18 | prev?: { path: string; title: string } 19 | toc: any 20 | } 21 | 22 | export default function PostLayout({ content, next, prev, children, toc }: LayoutProps) { 23 | const [loadComments, setLoadComments] = useState(false) 24 | 25 | const { path, slug, date, title, readingTime } = content 26 | return ( 27 | 28 | 29 | 30 |
    31 |
    32 |
    33 |
    34 |
    35 | {title} 36 |
    37 |
    38 |
    39 |
    Published on
    40 |
    41 | 发布于 42 | ·预估阅读{Math.ceil(readingTime.minutes)}分钟 43 |
    44 |
    45 |
    46 |
    47 |
    48 | 49 |
    50 |
    51 |
    {children}
    52 |
    53 | {siteMetadata.comments && ( 54 |
    55 | {!loadComments && ( 56 | 57 | )} 58 | {loadComments && } 59 |
    60 | )} 61 |
    62 |
    63 | {prev && ( 64 |
    65 | 70 | ← {prev.title} 71 | 72 |
    73 | )} 74 | {next && ( 75 |
    76 | 81 | {next.title} → 82 | 83 |
    84 | )} 85 |
    86 |
    87 |
    88 |
    89 |
    90 |
    91 | ) 92 | } 93 | -------------------------------------------------------------------------------- /data/blog/yarn---frozen-lockfile-installshibai.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: yarn --frozen-lockfile install失败 3 | date: 2021-07-26T12:34:20Z 4 | lastmod: 2021-07-26T12:34:36Z 5 | summary: 6 | tags: ["开发工具", "yarn", "install", "--frozen-lockfile"] 7 | draft: false 8 | layout: PostLayout 9 | images: ['/static/images/banner/yarn.png'] 10 | bibliography: references-data.bib 11 | --- 12 | 13 | ## 背景 14 | 15 | 公司目前端统一使用的包管理工具是yarn,项目都是结合gitlab-ci来构建与发布,为了在gitlab-cli内能过顺利的构建成功,统一在dockfile内install的时候添加`--frozen-lockfile`参数,如下所示 16 | 17 | ``` 18 | RUN npm config set registry https://registry.npm.taobao.org && \ 19 | yarn --frozen-lockfile --check-files --ignore-optional 20 | ``` 21 | 22 | 但是在某一位同学开发完,然后分支合并到test的时候构建失败了,错误信息如下 23 | 24 | ``` 25 | #14 0.794 yarn install v1.15.2 26 | #14 0.879 [1/4] Resolving packages... 27 | #14 1.849 error Your lockfile needs to be updated, but yarn was run with `--frozen-lockfile`. 28 | #14 1.849 info Visit https://yarnpkg.com/en/docs/cli/install for documentation about this command. 29 | #14 ERROR: executor failed running [/bin/sh -c npm config set registry https://registry.npm.taobao.org && yarn --frozen-lockfile --check-files --ignore-optional]: exit code: 1 30 | ------ 31 | > [builder_web 5/7] RUN npm config set registry https://registry.npm.taobao.org && yarn --frozen-lockfile --check-files --ignore-optional: 32 | ------ 33 | Dockerfile:8 34 | -------------------- 35 | 7 | 36 | 8 | >>> RUN npm config set registry https://registry.npm.taobao.org && \ 37 | 9 | >>> yarn --frozen-lockfile --check-files --ignore-optional 38 | 10 | 39 | -------------------- 40 | error: failed to solve: rpc error: code = Unknown desc = executor failed running [/bin/sh -c npm config set registry https://registry.npm.taobao.org && yarn --frozen-lockfile --check-files --ignore-optional]: exit code: 1 41 | ``` 42 | 43 | ## 解决过程 44 | 45 | 最开始关注的报错是这一行信息 46 | 47 | ``` 48 | error: failed to solve: rpc error: code = Unknown 49 | ``` 50 | 51 | 以为是淘宝镜像源的问题,最后换成npm源或者yarn源之后还是报这个错误,然后在回过头来看,发现最开始的报错在这一行 52 | 53 | ``` 54 | #14 1.849 error Your lockfile needs to be updated, but yarn was run with `--frozen-lockfile`. 55 | ``` 56 | 57 | 最后去查了下--frozen-lockfile参数的作用,如下所示 58 | 59 | >If you want to ensure yarn.lock is not updated, use --frozen-lockfile 60 | >Don’t generate a yarn.lock lockfile and fail if an update is needed. 61 | 62 | 意思就是当我们确认安装依赖的时候我们的yarn.lock是没有变化的,可以使用`--frozen-lockfile`参数,使用`--frozen-lockfile`参数的时候不会生成yarn.lock文件,且如果yarn在检测yarn.lock是有变化的时候,则会install失败 63 | 64 | 我们再来看下关于yarn.lock的作用 65 | 66 | >The yarn.lock file is utilized as follows: 67 | If yarn.lock is present and is enough to satisfy all the dependencies listed in package.json, the exact versions recorded in yarn.lock are installed, and yarn.lock will be unchanged. Yarn will not check for newer versions. 68 | If yarn.lock is absent, or is not enough to satisfy all the dependencies listed in package.json (for example, if you manually add a dependency to package.json), Yarn looks for the newest versions available that satisfy the constraints in package.json. The results are written to yarn.lock. 69 | 70 | 大概意思就是:如果 `yarn.lock` 存在并且足以满足 `package.json` 中列出的所有依赖项,则安装 yarn.lock 中记录的确切版本,且 `yarn.lock` 将保持不变。 Yarn不会检查更新的版本。 71 | 如果 `yarn.lock` 不存在,或者不足以满足 `package.json` 中列出的所有依赖项(例如,如果我们手动向 package.json 添加依赖项),则 Yarn 会查找满足 `package` 中约束的最新可用版本。 然后将结果写入yarn.lock。 72 | 73 | 所以结合起来就是yarn install的时候会判断当前项目是否存在yarn.lock文件,如果没有则install之后生成,如果有且有新的满足条件的依赖包,则安装新的满足条件的依赖包并更新yarn.lock文件;如果有且没有新的满足条件的依赖包则不安装新的依赖包且不更新yarn.lock文件;当我们install加上--frozen-lockfile参数的时候,就表示的yarn.lock内的依赖是不需要更新的,这就要求package.json内的依赖版本必须要与yarn.lock文件内的包依赖版本一致,如果不一致则会install失败,提示`error Your lockfile needs to be updated, but yarn was run with `--frozen-lockfile`` 74 | 75 | ## 总结 76 | 77 | 1. 排查问题的时候一定要定位清楚,错误来源哪里 78 | 2. `error Your lockfile needs to be updated`错误原因就是我们install的时候使用了--frozen-lockfile参数且手动改了package.json(比如"react":"^16.8.0"在package.json内将react版本手动改成"^16.10.0"),最后没有更新yarn.lock文件导致的报错 79 | 3. 排查--frozen-lockfile的报错,我们可以在本地调试 80 | - 在本地切到构建失败对应的分支 81 | - 删除node_modules 82 | - 执行yarn --frozen-lockfile命令,看是否有直接提示相同报错 83 | - 如果有相同报错,重新执行yarn install 84 | - 看到yarn.lock文件有更新 85 | - 在删除node_modules 86 | - 再次执行yarn --frozen-lockfile命令,是否可以正确install 87 | 88 | -------------------------------------------------------------------------------- /data/blog/jiluyiciphp-apache-mysql-composer-wampserveranzhuangguocheng.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 记录一次php、apache、mysql、composer、wampserver安装过程 3 | date: 2018-03-17T08:15:26Z 4 | lastmod: 2018-03-17T08:16:55Z 5 | summary: 6 | tags: ["PHP"] 7 | draft: false 8 | layout: PostLayout 9 | bibliography: references-data.bib 10 | --- 11 | 12 | 因为公司项目需求,然后在github上面找了一个headless cms directus,在安装directus的时候需要php、mysql、apache这三样东西,于是上网找了资料进行安装,遂把安装过程中出现的一些问题它记录下来;操作系统是windows。 13 | 14 | 15 | ## apache安装 16 | 17 | 1. 下载 下载地址http://httpd.apache.org/docs/current/platform/windows.html#down 18 | 19 | ![image](https://user-images.githubusercontent.com/20950813/37552827-4f6d9e44-29f7-11e8-9e8c-59cb04c43cf8.png) 20 | 21 | 点击去之后选择操作系统的位数,32还是64,然后点击下载; 22 | 23 | 2. 按照这个教程进行安装 https://www.cnblogs.com/Ai-heng/p/7289241.html 24 | ``` 25 | 启动命令 26 | httpd –k start 27 | 28 | 停止命令 29 | httpd –k stop 30 | ``` 31 | 32 | 3. 设置环境变量的目的是,让我们可以在cmd or git bash内直接运行某个服务or软件,而不需要每次都到对应文件的根目录or bin目录下去执行命令; 33 | 34 | ## php的安装 35 | 36 | 1. 下载 下载地址http://php.net/downloads.php 37 | 38 | ![image](https://user-images.githubusercontent.com/20950813/37552912-b22035c8-29f8-11e8-9507-557eddc39bc6.png) 39 | 40 | 点击windows download进入里面选择操作系统对应位数的版本,然后点击下载 41 | 42 | 2. 按照这个教程进行安装 https://www.cnblogs.com/Ai-heng/p/7289241.html 43 | 44 | ## mysql的安装 45 | 46 | 1. 下载 下载地址https://dev.mysql.com/downloads/mysql/ 47 | 48 | ![image](https://user-images.githubusercontent.com/20950813/37552933-4ab58996-29f9-11e8-8179-c18a576baef3.png) 49 | 50 | 操作系统对应位数的版本,然后点击下载 51 | 52 | 2. 按照这个教程进行安装http://blog.csdn.net/luomingjun12315/article/details/50863781 53 | 54 | ``` 55 | 启动mysql服务 56 | net start mysql 57 | 58 | 退出mysql命令 59 | mysql > \q 60 | 61 | 暂停mysql服务 62 | net stop mysql 63 | ``` 64 | 65 | ## composer安装 66 | 67 | 1. 下载 下载地址https://getcomposer.org/download/ 68 | 69 | ![image](https://user-images.githubusercontent.com/20950813/37552981-10f62aac-29fa-11e8-98bc-d61700d1b20a.png) 70 | 71 | 2. 按照这个教程进行安装http://blog.csdn.net/csdn_dengfan/article/details/54912039 72 | 73 | ## wampserver安装 74 | wampserver是一个集成了php、apache、mysql的工具,能够帮助我们快速搭建php开发环境; 75 | 76 | 1. 下载 按照此教程进行下载https://www.cnblogs.com/Sabre/p/6728818.html 77 | 78 | 2. 按照这个教程进行安装http://blog.csdn.net/wuguandi/article/details/53561253 79 | 80 | 3. 安装完成之后,就可以直接在该目录的www目录下进行开了 81 | 82 | 4. 修改www根目录及配置多目录访问 83 | ``` 84 | 1. 修改wampserver的安装目录,在打开里面的“script”文件夹,用记事本打开里面的config.inc.php 85 | // 注意,windows下表示路径的“\”在这里必须改为“/”) 86 | // $wwwDir = $c_installDir.'/www'; => $wwwDir = 'F:/directus-build' 新的根目录 87 | 88 | 2. 修改wamp目录下Apach目录下面的httpd.conf文件 89 | # DocumentRoot "${INSTALL_DIR}/www" 90 | # 91 | 92 | 替换成需要的新目录 93 | 94 | DocumentRoot "F:/directus-build/" 95 | 96 | 97 | 3. 修改wamp目录下Apach目录下面的httpd-vhosts.conf文件 98 | // 替换DocumentRoot与Directory 后面的路径 99 | 100 | ServerName localhost 101 | ServerAlias localhost 102 | DocumentRoot "F:/directus-build/" 103 | 104 | Options +Indexes +Includes +FollowSymLinks +MultiViews 105 | AllowOverride All 106 | Require local 107 | 108 | 109 | 110 | 111 | 到此为止就可以通过localhost访问新的www根目录了 112 | 113 | 4. 配置多站点,只需要在httpd-vhosts.conf文件内添加新的host 114 | 115 | 116 | ServerName www.abc.com 117 | ServerAlias www.abc.com 118 | DocumentRoot "F:/php-demo/" 119 | 120 | Options +Indexes +Includes +FollowSymLinks +MultiViews 121 | AllowOverride All 122 | Require local 123 | 124 | 125 | 126 | 5. 修改本机的hosts文件C:\Windows\System32\drivers\etc 127 | // 添加如下内容,以此类推,然后我们就可以通过www.abc.com来访问F:/php-demo/目录下的内容了 128 | 127.0.0.1 localhost 129 | 127.0.0.1 www.abc.com 130 | ``` 131 | 132 | 5. wampserver内的phpMyAdmin的初始登录名为root,密码为空 133 | 134 | 6. wampserver内mysql默认开启严格模式,可以直接使用设置选项禁用or自己修改my.ini文件,取消严格模式参考链接:https://www.cnblogs.com/lujs/p/6288806.html,设置严格模式 参考链接:http://blog.csdn.net/fdipzone/article/details/50616247 135 | 136 | 7. wampserver开启rewrite_module重写功能启用.htaccess文件,参考链接:http://blog.csdn.net/sgly2005/article/details/50718538 137 | 138 | 后端这条路上还是任重而道远啊! 139 | 140 | -------------------------------------------------------------------------------- /data/blog/vuexiangmuzhongzenmoyinrumockjs.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: vue项目中怎么引入mockjs 3 | date: 2018-01-24T12:54:17Z 4 | lastmod: 2018-02-28T08:26:14Z 5 | summary: 6 | tags: ["前端框架", "vue"] 7 | draft: false 8 | layout: PostLayout 9 | bibliography: references-data.bib 10 | --- 11 | 12 | 引入mockjs的目的是,提高我们的开发效率,不需要等待后端接口给出之后,才能够进行开发调试; 13 | 14 | mockjs两个最大的特点: 15 | 1. 够拦截ajax请求,保证我们能够快速开发,只需要在后端给出接口的时候,把接口替换就ok了; 16 | 2. 足够多的方法产生随机的不同类型的数据; 17 | 18 | ### 1. 安装 19 | ``` 20 | npm install --save mockjs 21 | ``` 22 | ### 2. 定义请求接口 23 | ``` 24 | 如loss-order.js 25 | // 这里的fetch是封装的基于axios的ajax请求 26 | import fetch from 'utils/fetch'; 27 | import CONFIG from '@/assets/js/config'; 28 | 29 | export function getLossOrderList (queryData) { 30 | const data = Object.assign({}, CONFIG.ajaxData, queryData); 31 | return fetch.getAjax('/loss_order/loss_order_One/lossOrderList', data); 32 | }; 33 | ``` 34 | ### 3. 利用mockjs定义返回的接口数据 35 | 1. 第一种方法,利用mockjs的Random方法来构造函数 36 | 2. 第二种方法,利用Mock.mock并加@来构造数据 37 | 38 | ``` 39 | 第一种方式 40 | import { paramURL } from '@/utils'; 41 | import Mock from 'mockjs'; 42 | 43 | const Random = Mock.Random; 44 | 45 | export default { 46 | getLossOrderList: (config) => { 47 | const param = paramURL(config.url); 48 | let result = {}; 49 | result.key = ['订单号', '销售平台', 'SKU', '产品品牌', '所属仓库', '订单类型', '平台订单号', '物流方式\跟踪号', '出货状态', '完成状态', '金额', '北京付款时间']; 50 | const value = []; 51 | const count = 100; 52 | const start = (Number(param.offset) - 1) * Number(param.limit); 53 | const end = Number(param.offset) * Number(param.limit); 54 | // 第一种造数据的方式,引入Random来进行造数据 55 | for (let i = 0; i < count; i++) { 56 | value.push({ 57 | orderId: Random.increment(), 58 | orderNo: 'CO' + Random.now('day', 'yyyyMMdd') + 'LZD', 59 | salesPlat: Random.first(), 60 | sku: Random.float(0, 100000000000, 2), 61 | skuId: Random.increment(), 62 | productBrand: Random.cword('零一二三四五六七八九十', 3), 63 | warehouse: Random.cword('光明清溪', 2), 64 | warehouseId: '172', 65 | orderType: '普通', 66 | platOrderNo: Random.integer(0), 67 | logistics: Random.cword('零一二三四五六七八九十', 5), 68 | sailStatus: '未出货', 69 | complateStatus: '备货中', 70 | price: Random.float(0, 100, 4) + 'USD', 71 | payTime: Random.now('second') 72 | }) 73 | } 74 | 75 | // 第二种方式直接使用Mock.mock并加@来构造数据 76 | for (let i = 0; i < count; i++) { 77 | value.push(Mock.mock({ 78 | orderId: '@increment', 79 | orderNo: 'CO' + '@now("day", "yyyyMMdd")' + 'LZD', 80 | salesPlat: '@first', 81 | sku: '@float(0, 100000, 2, 4)', 82 | skuId: '@increment()', 83 | productBrand: "@cword('零一二三四五六七八九十', 3)", 84 | warehouse: '@cword("光明清溪", 2)', 85 | warehouseId: '172', 86 | orderType: '普通', 87 | platOrderNo: '@integer(0)', 88 | logistics: '@cword("零一二三四五六七八九十", 5)', 89 | sailStatus: '未出货', 90 | complateStatus: '备货中', 91 | price: '@float(0, 100, 2, 4)' + 'USD', 92 | payTime: '@now("second")' 93 | })) 94 | } 95 | 96 | result.value = value.slice(start, end); 97 | result.pagingData = { 98 | limit: +param.limit, 99 | offset: +param.offset, 100 | total: count 101 | } 102 | return result; 103 | } 104 | } 105 | ``` 106 | 107 | ### 4. 利用mock定义响应接口 108 | ``` 109 | import Mock from 'mockjs'; 110 | import lossOrderAPI from './loss-order'; 111 | 112 | Mock.setup({ 113 | // 指定被拦截的 Ajax 请求的响应时间,单位是毫秒 114 | timeout: '350-600' 115 | }); 116 | 117 | // 亏损订单接口 118 | Mock.mock(/\/loss_order\/loss_order_One\/lossOrderList/, 'get', lossOrderAPI.getLossOrderList); 119 | export default Mock; 120 | ``` 121 | 122 | 参考链接:https://github.com/nuysoft/Mock/wiki 123 | -------------------------------------------------------------------------------- /data/blog/shiyongeslint+husky+lint-staged+prettiergoujiantongyidedaimafenggejidaimajianchagongzuoliu.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 使用eslint+husky+lint-staged+prettier构建统一的代码风格及代码检查工作流 3 | date: 2018-11-10T14:04:23Z 4 | lastmod: 2018-11-11T14:01:35Z 5 | summary: 6 | tags: ["开发工具", "eslint", "husky", "lint-staged", "代码检查"] 7 | draft: false 8 | layout: PostLayout 9 | bibliography: references-data.bib 10 | --- 11 | 12 | #### 当我们团队比较大,且人数比较多的时候,因为同事之间不同的代码风格及编辑器,会导致项目内的代码,无法形成统一的风格,不利于阅读,也更容易造成bug;为保持统一的代码风格及减少书写上的bug,所以抽时间,去整理通过什么样的工具和方式去实现团队统一的代码风格及代码检查工作流; 13 | 14 | #### 一、引入husky,在我们commit之前做eslint的检查,注意这是对lint命令内所有的文件做eslint检查 15 | husky是一个git 钩子插件,提供了pre-commit pre-push等封装好了的钩子,我们可以在这些钩子触发的时候执行某些命令或者操作 16 | 17 | 使用方式 18 | ``` 19 | yarn add husky --dev | npm install husky --save-dev 20 | 21 | // 第一种方式直接写在package.json内 22 | { 23 | "husky": { 24 | "hooks": { 25 | "pre-commit": "yarn lint", 26 | "...": "..." 27 | } 28 | } 29 | } 30 | 31 | // 第二种方式直接在根目录下建立.huskyrc, .huskyrc.json or .huskyrc.js等文件,如果使用这种方式husky的版本需要大于1.0.0 32 | 33 | // .huskyrc 34 | { 35 | "hooks": { 36 | "pre-commit": "yarn lint" 37 | } 38 | } 39 | ``` 40 | 41 | #### 二、husky只是提供了提交时的钩子,然而有时候我们处理的项目并不是新项目,这个时候,可能只想对本次提交的代码,做代码检查,而不是对现有目录内所有的文件做检查,所以我们需要引入lint-staged这个插件,lint-staged插件提供了对本次提交的各种文件检查的检查方式,如js,css,saas等; 42 | 43 | 使用方式 44 | ``` 45 | yarn add lint-staged --dev | npm install lint-staged --save-dev 46 | 47 | // 第一种方式直接写在package.json内 48 | "husky": { 49 | "hooks": { 50 | "pre-commit": "lint-staged" 51 | } 52 | }, 53 | "lint-staged": { 54 | "linters": { // linters 被匹配的项及匹配之后需要执行的命令 55 | "*.{js,vue}": [ // 允许按命令执行多项命令 56 | "eslint --fix", // 也可以直接时script内的命令,如yarn lint,这里需要注意的时当执行script内的命令时,检查的就是script命令内的所有文件了 57 | "git add" 58 | ], 59 | "*.css": "stylelint", 60 | "*.scss": "stylelint --syntax=scss" 61 | }, 62 | "ignore": [] 63 | }, 64 | 65 | // 第二种方式可以在根目录下建立lintstagedrc or lint-staged.config.js 文件 66 | { 67 | "linters": { 68 | "*.{js,vue}": [ 69 | "eslint --fix", 70 | "git add" 71 | ] 72 | }, 73 | "ignore": [] 74 | } 75 | 76 | ``` 77 | 78 | #### 三、引入prettier插件格式化代码,当我们做完了前面两步之后,还不能保证团队内的代码是一样的,只是保证了团队内的代码检查是符合同一份eslint规则的,如果要让代码不论在哪个同时的编辑器内打开是一样的,则需要继续引入prettier插件 79 | 80 | 这里说下eslint与prettier之间的区别,二者的侧重点不同,前者是代码规范检查,如是否可以使用var,尾逗号,函数括号前面是否留空格,便于团队保持比较统一的代码风格;而prettier则是代码格式化插件,可以根据一定的规则对我们的js、css、less、jsx、vue等文件进行格式化,保证团队输出的代码是统一的;所以二者除了小部分规则有交集之外,二者是可以在我们的开发种相辅相成的; 81 | 82 | 引入方式 83 | ``` 84 | // 第一种方式直接安装prettier插件 85 | yarn add prettier --dev | npm install prettier --save-dev 86 | 87 | 在根目录下建立.prettierrc配置文件 88 | { 89 | "tabWidth": 4, 90 | "semi": false, 91 | "singleQuote": true, 92 | "parser": "flow", // 默认格式化js的方式 93 | "printWidth": 100, 94 | } 95 | 96 | 在package.json内配合husky、lint-staged使用 97 | "husky": { 98 | "hooks": { 99 | "pre-commit": "lint-staged", 100 | "post-commit": "git update-index --again" 101 | } 102 | }, 103 | "lint-staged": { 104 | "linters": { 105 | "*.js": ["prettier --parser flow --write", "eslint --fix", "git add"], // 格式化js文件 106 | "*.vue": ["prettier --parser vue --write", "eslint --fix", "git add"], // 格式化vue文件 107 | "*.json": ["prettier --parser json --write", "git add"] // 格式化json文件 108 | }, 109 | "ignore": ["dist/**/*.js"] // 忽略掉dist目录下的文件 110 | }, 111 | 112 | 还可以使用单独的命令 113 | "pt": "prettier --debug-check {src,test}/**/*.js", 114 | "pt:vue": "prettier --parser vue --debug-check {src,test}/**/*.vue", 115 | "pt:fix": "prettier --write {src,test}/**/*.js", 116 | "pt:fixvue": "prettier --parser vue --write {src,test}/**/*.vue" 117 | 118 | 注意这里的parser有好几种,我在配置的时候,好像不能根据文件自动识别,然后进行格式化,如parser方式为flow时,无法格式化vue文件,所以vue文件需要用parser引擎指定为vue 119 | 120 | // 第二种方式则是使用eslint-plugin-prettier 具体的配置方式参考prettier提供的文档 121 | 122 | ``` 123 | 124 | ### 总结 125 | 126 | 团队的代码规范化只有当我们真正碰到的时候,才会去认真对待及找寻方法进行解决,所以趁此机会记录一下,并让我们在以后开始任何新项目or接手任何项目的时候,能够时刻把代码规范化这些能过提升我们工作效率及减少bug的方式加入进去; 127 | 128 | 129 | 参考链接: 130 | https://prettier.io/docs/en/install.html 131 | https://github.com/okonet/lint-staged 132 | https://github.com/typicode/husky 133 | -------------------------------------------------------------------------------- /data/blog/jsneihuoquchicundeshuxingorfangfahuizong.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: js内获取尺寸的属性or方法汇总 3 | date: 2018-01-05T12:41:55Z 4 | lastmod: 2018-01-29T09:37:21Z 5 | summary: 6 | tags: ["原生JS"] 7 | draft: false 8 | layout: PostLayout 9 | bibliography: references-data.bib 10 | --- 11 | 12 | 最近在项目中开发自定义滚动条组件,用到了拖拽及滚动,而用到拖拽及滚动的时候就不得不用到一些获取元素尺寸、滚动高度、鼠标位置、元素距离视口的位置等属性,于是写完组件之后抽了个时间总结了下,希望加深理解 13 | 14 | ![image](https://user-images.githubusercontent.com/20950813/34636413-9be914f4-f2db-11e7-8bcb-c7c6a5d84511.png) 15 | 16 | 17 | ## 标准盒模型 18 | 1. clientWidth = width + padding - scrollBarWidth; 因为滚动条会在border内也就是padding所占位置 19 | 2. offsetWidth = width + padding + border = clientWidth + border ,这里不需要另外加上scrollBarWidth的原因就是scrollBarWidth占用的宽度就是元素自身的width or padding 20 | 3. scrollWidth元素可滚动宽度,不包含元素的margin与border,scrollLeft = ( srollWidth - clientWidth )之间的值 21 | 同理clientHeight offsetHeight scrollHeight是一样的 22 | 4. scrollTop 属性可以获取或设置一个元素的内容垂直滚动的像素数。 注意是这个元素的顶部到它的最顶部可见内容(的顶部)的距离的度量,而不是到视口的高度 23 | 24 | ## box-size: border-box;盒模型 width包含了padding与border 25 | 1. offsetWidth = width; 26 | 2. clientWidth = width - border - scrollBarWidth = offsetWidth - border - scrollBarWidth 27 | 28 | 同理其它属性值是一样的 29 | 30 | ## getBoudingClientRect()获取到的盒模型 兼容ie9+ 31 | 1. .width = offsetWidth; 32 | 2. .height = offsetHeight; 33 | 3. .left = 元素左边框到视口的左侧距离; 34 | 4. .top = 元素上边框到视口的上侧距离; 35 | 5. .right = .left + .width; 36 | 6. .bottom = .top + .height 37 | 38 | ## 事件对象event获取的位置属性 39 | 1. offsetX offsetY 相对于target元素边框的位置 40 | 2. pageX pageY 相对于当前页面(整个文档)的左上角的位置 当有滚动条时包含了scrollTop and scrollLeft 41 | 3. clientX clientY 相当于当前可视屏幕的左上角位置 42 | 43 | ## offset定位系列位置属性 44 | 1. offsetLeft: 获取当前元素左侧边框到offsetParent的左侧边框内侧距离 45 | 2. offsetTop: 获取当前元素上侧边框到offsetParent的上侧边框内侧距离 46 | 3. offsetParent: 指向最近的包含改元素的定位元素,如果没有定位元素则为最近的table or table cell or 根元素(标准模式下为 html;quirks 模式下为 body),当元素设置为dispaly:none 时 offsetParent为null 47 | 48 | ## 常用的一些场景 49 | 1. 判断元素是否出现滚动条 50 | 根据scorllHeight、scrollWidth与clientHeight、clientWidth的大小关系来进行判断,因为二者获取到的宽高是相同的部分,所有没有滚条条时scrollHeight = clientHeight; scrollWidth = clientWidth; 51 | 判断水平方向滚动条 scrollWidth > clinetWidth 允许滚动的水平范围scrollLeft => scrollWidth - clientWidth 52 | 判断垂直方向滚条条 scrollHeight > clientHeight 允许滚动的垂直范围scrollTop => scrollHeight - clientHeight 53 | 54 | 2. 拖拽 55 | 在mousedown的时候获取鼠标点击的当前位置 56 | 在当前屏幕内拖拽 57 | disX = e.clientX - e.target.offsetLeft 或者直接写e.offsetX; 58 | disY = e.clientY - e.target.offsetTop 或者直接写e.offsetY; 59 | 在整个文档内拖拽 60 | disX = e.pageX - e.offsetX; 61 | disY = e.pageY - e.offsetY; 62 | 在mousemove的时候 63 | left = e.clinetX - disX; 64 | top = e.clinetY - disY; 65 | 66 | 3. 获取当前屏幕的尺寸 67 | screenWidth = document.documentElement.clientWidth || document.body.clientWidth; 68 | screenHeight = document.documentElement.clientHeight || document.body.clientHeight; 69 | 70 | 4. 监听window对象上的scroll事件时获取浏览器滚动条的scrollTop 71 | scrollTop = document.documentElement.scrollTop || document.body.scrollTop; 72 | scrollLeft = document.documentElement.scrollLeft || document.body.scrollLeft; 73 | 74 | 5. 获取浏览器滚动条的宽度 75 | 第一种思路设置元素overflow为scroll,计算设置scroll之前的clientWidth与之后的clientWidth,之差就是滚动条的宽度,原因是clientWidth是不包含滚动条宽度的,且有滚动条之后滚动条会占据clientWidth的宽度; 76 | ``` 77 | function getScrollBar () { 78 | var div = document.createElement('div'), 79 | noScrollWidth, 80 | scrollWidth; 81 | div.style.width = '200px'; 82 | div.style.height = '300px'; 83 | div.style.position = 'absolute'; 84 | div.style.left = '99999px'; 85 | noScrollWidth = document.body.appendChild(div).clientWidth; 86 | div.style.overflowY = 'scroll'; 87 | scrollWidth = div.clientWidth; 88 | document.body.removeChild(div); 89 | return noScrollWidth - scrollWidth; 90 | } 91 | ``` 92 | 93 | 第二个思路直接设置overflow:scroll,然后使用offsetWidth - clientWidth之差来获取滚动条的宽度,因为offsetWidth是包含滚动条的,而clinetWidth是不包含滚动条宽度的 94 | ``` 95 | function getScrollBar () { 96 | var div = document.createElement('div'), 97 | clientWidth, 98 | offsetWidth; 99 | div.style.width = '200px'; 100 | div.style.height = '300px'; 101 | div.style.overflowY = 'scroll' 102 | div.style.position = 'absolute'; 103 | div.style.left = '99999'; 104 | clientWidth = document.body.appendChild(div).clientWidth; 105 | offsetWidth = div.offsetWidth; 106 | return offsetWidth - clientWidth; 107 | } 108 | ``` 109 | 110 | -------------------------------------------------------------------------------- /data/blog/inputyuansuxianzhishuruzhongwenneirongshichuxiandejieduanwenti.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: input元素限制输入中文内容时出现的截断问题 3 | date: 2019-05-07T12:00:22Z 4 | lastmod: 2019-11-17T02:00:45Z 5 | summary: 6 | tags: ["原生JS", "input"] 7 | draft: false 8 | layout: PostLayout 9 | bibliography: references-data.bib 10 | --- 11 | 12 | ## 背景 13 | 14 | input输入框只允许输入中文字母数字,不允许输入其它字符 15 | 16 | ## 解决方案 17 | ### 第一种方案 18 | 19 | 使用input事件,在安卓是ok的,但是在ios及pc端mac及window下输入中文时,会直接输入字母而不是 20 | 想输入的中文,如下图所示 21 | 22 | ```js 23 | 24 | 25 | const regHanDataAz = /[^\da-zA-Z\u4e00-\u9fa5]/ 26 | const ele = document.getElementById('name14') 27 | ele.addEventListener('input', function(e) { 28 | e.target.value = e.target.value.replace(regHanDataAz, '') 29 | } 30 | }) 31 | ``` 32 | 33 | ![input](https://user-images.githubusercontent.com/20950813/57299407-0b4fce00-7107-11e9-9a5c-55d37b1bf4f4.gif) 34 | 35 | ### 第二种方案 36 | 37 | 使用keyup事件,在安卓是ok的,但是在ios及pc端mac及window下输入中文时,会直接输入字母而不是想输入的中文,如下图所示 38 | 39 | ```js 40 | 41 | 42 | const regHanDataAz = /[^\da-zA-Z\u4e00-\u9fa5]/ 43 | const ele = document.getElementById('name14') 44 | ele.addEventListener('keyup', function(e) { 45 | e.target.value = e.target.value.replace(regHanDataAz, '') 46 | } 47 | }) 48 | ``` 49 | ![input+keyup](https://user-images.githubusercontent.com/20950813/57299411-0be86480-7107-11e9-86bd-8541f433a3a7.gif) 50 | 51 | ### 第三种方案 52 | 53 | 使用input事件,及compositionstart、compositionend事件,在安卓、ios及pc端mac及window下都是ok的,如下图所示 54 | 55 | ```js 56 | 57 | 58 | const regHanDataAz = /[^\da-zA-Z\u4e00-\u9fa5]/ 59 | var lock = false 60 | const ele = document.getElementById('name14') 61 | ele.addEventListener('input', function(e) { 62 | if (!lock) { 63 | fn3(e) 64 | } 65 | }) 66 | // input 事件会截断非直接输入,所以通过compositionstart及compositionend事件来排除非直接输入 67 | // 影响,避免想输入汉字的时候,直接成字母了 68 | ele.addEventListener('compositionstart', function () { 69 | lock = true; 70 | }) 71 | ele.addEventListener('compositionend', function (e) { 72 | lock = false; 73 | fn3(e) 74 | }) 75 | 76 | function fn3(e) { 77 | e.target.value = e.target.value.replace(regHanDataAz, '') 78 | } 79 | ``` 80 | ![input+compositionend](https://user-images.githubusercontent.com/20950813/57299410-0be86480-7107-11e9-9276-a23c51965e79.gif) 81 | 82 | ## 项目中实际使用例子 83 | 84 | ```js 85 | 86 | 87 | .directive('filterEnter', ['$compile', ($compile) => { 88 | return { 89 | restrict: "EA", 90 | scope: { 91 | content: '=' 92 | }, 93 | link(scope, ele, attr) { 94 | // 仅允许输入汉子字母数字 95 | const regHanDataAz = /[^\da-zA-Z\u4e00-\u9fa5]/g 96 | function validInput() { 97 | var lock = false 98 | ele[0] && ele[0].addEventListener('input', function(e) { 99 | if (!lock) { 100 | console.log('ccc'); 101 | validing(e) 102 | } 103 | }) 104 | // input 事件会截断非直接输入,所以通过compositionstart及compositionend事件来排除非直接输入 105 | // 影响,避免想输入汉字的时候,直接成字母了 106 | ele[0] && ele[0].addEventListener('compositionstart', function () { 107 | lock = true; 108 | }) 109 | ele[0] && ele[0].addEventListener('compositionend', function (e) { 110 | lock = false; 111 | validing(e) 112 | }) 113 | 114 | function validing(e) { 115 | e.target.value = e.target.value.replace(regHanDataAz, '') 116 | scope.content = e.target.value 117 | scope.$digest() 118 | } 119 | } 120 | // 通过notValid属性来开启是否允许输入特殊字符,默认开启 121 | !attr.notValid && validInput() 122 | const maxLength = attr.maxlength || 20 123 | const template = `{{(content && content.length) || 0}}/${maxLength}` 124 | ele.parent().append($compile(template)(scope)) 125 | } 126 | } 127 | }]); 128 | ``` 129 | 130 | -------------------------------------------------------------------------------- /data/blog/iframe-postMessage-kuayu-cookiecaozuo.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: iframe postMessage 跨域 cookie操作 3 | date: 2018-12-12T14:58:42Z 4 | lastmod: 2018-12-12T15:16:53Z 5 | summary: 6 | tags: ["原生JS", "iframe", "postMessage", "跨域", "cookie"] 7 | draft: false 8 | layout: PostLayout 9 | bibliography: references-data.bib 10 | --- 11 | 12 | iframe postMessage 跨域 cookie操作 13 | 14 | 有一个这样的场景,两个站点之间是通过嵌套的方式进行关联;A站点维护一套账号体系,B站点也维护一套账号体系;但是二者是打通的,即如果在B站点内的注册的账号一定会在B站点内有一个对应的关联账号;而我们这种方式即运行在app内,也可以运行在h5上;而app内是底层一些清除cookie的方法,所以当A账号退出登陆时,也会清除B站点域下的cookie;而h5上是通过A退出登陆时,会通知到我们后台A账号退出登陆了,这是我们后台会把A对应的B站点下的账号的cookie清掉;这样就可以保持二者的登录态是统一的;但是这里漏了一种场景,即A账号不是主动退出时;我们后端是没有收到A账号退出登陆的消息的,所以我们换个账号登录时,在B站点下就会存在串账号的问题,因为B站点下还有之前A账号对应的cookie;所以为了解决这个问题,一是排查A站点下为什么会无故退出;二是采取补偿措施,就是在A站点登录的时候,都会主动去清一次B站点下的cookie; 15 | 16 | #### 第一次实践方式,直接在A站内调用清除B站点下cookie的接口;实际上接口是调用成功,但是cookie没有被清掉;原因是ajax接口是在A域下调用的,存在跨域问题,通过在A站点不能清除掉B站点下的cookie; 17 | 18 | #### 第二种实践引入iframe,引入一个隐藏的B站点iframe,然后在iframe内直接操作B站点的cookie;另一种方式是通过postMessage来发送消息,然后在message监听回调内进行cookie操作;因为接口后端直接提供了接口,所以没有直接操作是否能直接删除cookie;直接使用的是调用后台提供的接口来清除cookie; 过程如下 19 | 20 | #### 在使用postMessage方式之前先看下postMessage方法的兼容性 21 | 22 | pc端 23 | 24 | ![image](https://user-images.githubusercontent.com/20950813/49878265-1b1dfb80-fe62-11e8-887f-6aec5691d86d.png) 25 | 26 | 移动端 27 | 28 | ![image](https://user-images.githubusercontent.com/20950813/49878304-34bf4300-fe62-11e8-8773-3480376f37ce.png) 29 | 30 | #### 从can i use上看pc端兼容ie8+,移动端兼容ios4.0+、安卓2.1+,所以我们可以放心大胆的使用postMessage方法 31 | 32 | #### 1 在A站点引入一个隐藏的iframe,因为postMessage是window对象上的方法,所以需要先通过contentWindow获取iframe内的window对象 33 | 34 | ``` 35 | // 创建iframe,并想iframe内发送消息 36 | function createIframe () { 37 | const iframe = document.createElement('iframe') 38 | iframe.id="my_iframe" 39 | iframe.className = 'my-iframe' 40 | iframe.src = 'xxxx' 41 | iframe.onload = function () { 42 | // 这里需要注意两个地方,第一个参数是传递的参数,最好是字符串,如果是对象使用JSON.stringfiy处理一下,第二个参数表示可以接收到postMessage传递过去消息的域,如果不想指定就写上*,不过为了安全性,最好写上域 43 | iframe.contentWindow.postMessage('message', 'xxxx') 44 | } 45 | iframe.onerror = function () {} 46 | document.body.appendChild(iframe) 47 | } 48 | 49 | // 监听所有iframe内发送来的消息 50 | function receiveIframeMessage () { 51 | window.addEventListener('message', function (event) { 52 | if (event.origin === 'xxx' && event.data === 'yyy') { 53 | removeIframe() 54 | } 55 | }, false) 56 | } 57 | 58 | // 删除创建的iframe 59 | function removeIframe () { 60 | const iframe = document.getElementId('my_iframe') 61 | document.body.removeChild(iframe) 62 | } 63 | ``` 64 | 65 | #### 2 B站点下设置监听message回调即可,并且在操作成功之后,在利用postMessage发送回去 66 | ``` 67 | window.addEventListener('message', function (event) { 68 | if (event.origin === 'xxx' && event.data === 'yyy') { 69 | clearCookie().then(() => { 70 | window.parent.postMessage('cookie清除成功', '*') 71 | }).catch(() => { 72 | window.parent.postMessage('cookie清除失败', '*') 73 | }) 74 | } 75 | }, false) 76 | ``` 77 | 78 | ### 总结 79 | 80 | 在本地调试正常之后,构建到测试环境的时候,发现居然没有生效,A站成功发送来消息,但是B站点监听不到消息postMessage发送过来的消息,一开始以为代码写错了,以及是发送的时候域写错了,把域改成*还是收不到;后面又一以为设置监听的地方不对,试了几个地方都没找到原因,后面在想,本地都能调通,说明代码应该是没什么问题的,那就是接收的时候有问题,会不会是A站发送的时候,B站点内还没设置好监听呢?因为使用的vue,然后在mounted钩子内监听的,于是在A站点内发送消息的时候给个延迟,果然就收到了,所以这里修改了下,采用轮询的方式来发送消息,知道收到B站点反馈过来已经操作成功的消息就结束; 81 | 82 | ``` 83 | function createIframe () { 84 | const iframe = document.createElement('iframe') 85 | iframe.id="my_iframe" 86 | iframe.className = 'my-iframe' 87 | iframe.src = 'xxxx' 88 | var timer = null; 89 | var myPostMessage = function () { 90 | // 轮询的目的是,iframe内还没设置好监听,避免通信不成功 91 | timer = setTimeout(() => { 92 | var iframeEle = document.getElementById('my_iframe') 93 | iframeEle && iframe.contentWindow.postMessage('message', `xxxx`) 94 | if (iframeEle) { 95 | myPostMessage() 96 | } 97 | }, 500) 98 | } 99 | iframe.onload = function () { 100 | myPostMessage() 101 | } 102 | iframe.onerror = function () { 103 | console.log('iframe onerror') 104 | } 105 | document.body.appendChild(iframe) 106 | } 107 | ``` 108 | 109 | 跨域的行为分为3类 110 | 1. 获取网页存储信息,如cookie、localstorage,如iframe内的cookie及localstorage 111 | 2. 操作dom,如iframe内的dom 112 | 3. ajax操作,然后会有什么样的反应,能不能携带cookie,需要什么设置 113 | 上面这三种行为需要好好总结下 114 | 115 | 链接 116 | https://developer.mozilla.org/zh-CN/docs/Web/API/Window/postMessage 117 | 118 | -------------------------------------------------------------------------------- /data/blog/xiangmuzhongzenmopeizhiwebpack.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 项目中怎么配置webpack 3 | date: 2017-12-11T11:55:50Z 4 | lastmod: 2017-12-11T11:55:50Z 5 | summary: 6 | tags: ["开发工具", "webpack"] 7 | draft: false 8 | layout: PostLayout 9 | bibliography: references-data.bib 10 | --- 11 | 12 | 记得刚开始使用webpack的时候,自己踩了些坑,最近恰好想在捋下webpack,所以就记录下来。 13 | 下面列一下一些碰到的疑问 14 | 15 | 1. **开发的时候webpack怎么不支持index.html页面的热更新** 16 | 因为webpack-dev-server只处理与入口文件有依赖的资源,而我们在各个模块内一般都是引入js资源or css资源,img资源,字体资源,视频音频资源,所有热更新无法覆盖到项目目录下的所有文件,只有与入库文件有依赖关系的资源才会被监视,才会在修改的时候去进行热更新(启用了热更新的前提) 17 | 解决方法是:引入html-withimg-loader,在rules内配置该loader,同时在入库文件内引入index.html即可实现index.html的热更新 18 | 19 | 2. **webpack打包的时候,html页面内img引入的图片路径没有做处理** 20 | 这是webpack本身就不提供这个选项,解决方法有两个 21 | 第一个方法,在js文件内引入改图片资源如import imgURL from './1.jpg'然后通过dom来进行操作 22 | 第二个方法是使用html-withimg-loader这个loader,在rules内配置规则即可,需要注意的时这个loader 23 | 只能处理img标签的src属性,不能处理video等标签的资源引入,所以video的标签资源的引入还需要动 24 | 态设置 25 | 26 | 3. **output配置项内的path及publicPath有什么区别** 27 | path参数的含义是指定入口文件输出的路径(文件夹),如path: resolve(__dirname, 'dist'), //__dirname webpack执行时的文件目录,这里就是跟目录,也就是输出到根目录下的dist文件夹,即改配置属性不会去影响资源属性的路径,记住一点path是webpack所有文件的输出的路径,必须是绝对路径,比如:output输出的js,url-loader解析的图片,HtmlWebpackPlugin生成的html文件,都会存放在以path为基础的目录下; 28 | publicPath参数的含义是指补全生成的index.html(href,script等引入的路径,但不包括img引入的图片路径)及各个css,文件内引入图片,音频等资源的路径,如publicPath:'http://127.0.0.1:8020/es6/myEs6/dist/',那么在打包的时候引入资源文件的路径就是publicPath + 开发的时候写的相对路径,如background-image: url('../../static/images/4.jpg');打包后的路径为background-image:url(http://127.0.0.1:8020/es6/myEs6/dist/static/images/4.74ca2aa.jpg);如果pablicPath不配置则默认为''字符串,则html及css内加载的图片资源路径就是开发时写的相对路径去掉.变成绝对路径,如果publicPath配置为./则,则css内图片路径会会去掉相对路径./or ../而html内引入的css及js文件则会生成./文件名,如果publicPath配置成/则css内即html内引入资源的路径都会是绝对路径background-image:url(/static/images/4.74ca2aa.jpg)即publicPath的作用就是为index.html及css文件内引入图片,音频等文件添加访问路径前缀(静态资源最终访问路径 = output.publicPath + 资源loader或插件等配置路径),如css内引用图片的路径为url('../images/4.jpg'),publicPath为/webpack/webpack-src/dist/那么最终输出的路径为/webpack/webpack-src/dist/static/images/4.415452jpg 29 | 30 | 4. **环境变量process.env.NODE_ENV怎么去获取到具体的值** 31 | 我们在webpack内使用环境变量的作用是帮助我们进行区分开发环境、测试环境及正式环境,那么要在webpack的配置文件内哪到值需要设置两个地方 32 | 第一处是 33 | `new webpack.DefinePlugin({ 34 | 'process.env': { 35 | NODE_ENV: '"development"' // 定义环境变量 36 | } 37 | })` 38 | 39 | 第二处是 40 | "dev": `set NODE_ENV=development&&webpack-dev-server --open ` 41 | 42 | 5. **url-loader与file-loader是什么关系** 43 | url-loader的作用是处理css内orjs内引入的图片资源,当图片资源小于limit时,url会将图片转换为base64的图片进行输出,如果资源大于limit则不进行转换,将使用file-loader处理以图片的路径输出,此时可以指定图片输出的路径name: 'static/images/[name].[hash:7].[ext]',注意这里的路径是基于output内的path开始的,最终输出的路径为path/static/images/[name].[hash:7].[ext] 44 | file-loader的作用是处理图片音频等资源并输出,url-loader的基础,注意的时可以直接配置outputPath,publicPath,useRelativePath等配置属性 45 | 46 | 6. **音频视频文件怎么引入** 47 | 配置loader,然后使用require or import导入 48 | 49 | 7. **HtmlWebpackPlugin插件内的template属性与inject属性具体作用是什么** 50 | template: ‘index.html’ //需要参考的模板 注意前面可以带路径‘views/index.html’,注意这里是相当于根路径 51 | inject: 'body' // 向template或者templateContent中注入所有静态资源,true或者body:所有JavaScript资源插入到body元素的底部;head: 所有JavaScript资源插入到head元素中;false: 所有静态资源css和JavaScript都不会注入到模板文件中 52 | 53 | 8. **使用webpack-dev-server搭建本地服务器时,它的contentBase属性与publicPath属性有什么作用** 54 | contentBase: path.join(__dirname, 'dist') // 告诉webpack-dev-server,在 localhost:8080 下建立服务,服务的文件来自dist目录,这里需要注意的时webpack-dev-server默认生成的dist目录是在内存中,不是项目目录内的dist 55 | publicPath的作用output内的publicPath作用类似 56 | 57 | 9. **webpack内的chunk、hash、chunkhash分别是什么意思** 58 | chunk:表示一个文件,默认情况下webpack的输入是一个入口文件,输出也是一个文件,这个文件就是一个chunk,chunkId就是产出时给每个文件一个唯一标识id,chunkhash就是文件内容的md5值 59 | hash:计算所有 chunks 的 hash,及这一次构建过程的hash,适合 chunk 拆分不多的小项目,但所有资源全打上同一个 hash,无法完成持久化缓存的需求 60 | chunkhash: 每个文件生成时的chunkhash,即这一次构建的过程中每个文件生成的hash;JS 资源的 [chunkhash] 由 webpack 计算,Images/Fonts 的 [hash] 由 webpack/url-loader计算,css的hash由extract-text-webpack-plugin计算给出,注意生成hash值是一个编译的过程,所有在开发的时候不需要给输出文件配置hash值 61 | 62 | 10. **webpack内置的CommonsChunkPlugin有什么作用** 63 | 抽出公共模块,这里我们的代码js代码一般分为三类,1.引入的第三方框架和库;2.自己写的公共代码;3.其它的单独js文件,我们需要将不经常变的第一类js文件or第二类js文件抽离到公共的一个js文件内,保证浏览器的长时间缓存,提高加载效率 64 | 65 | 11. **package.json内怎么去配置webpack命令** 66 | "build": "set NODE_ENV=production&&webpack --progress --hide-modules --color --config webpack.prod.conf.js", 67 | "dev": "set NODE_ENV=development&&webpack-dev-server --open --inline --hot --compress --history-api-fallback --progress --color --config webpack.dev.conf.js" 68 | 注意的是webpack启动时默认会去找跟目录下的webpack.config.js文件,而我们如果要让定制webpack.base.conf.js等配置文件生效,则需要在命令行处配置读取文件用--config来进行配置 69 | 70 | 71 | 参考链接 72 | https://webpack.js.org/concepts/ 73 | https://doc.webpack-china.org/concepts/ 74 | -------------------------------------------------------------------------------- /components/toc/AnchorLink.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useRef, useState } from 'react' 2 | import { throttle } from 'lodash' 3 | 4 | interface LinkProps extends Omit, 'href'> { 5 | reloadDocument?: boolean 6 | replace?: boolean 7 | state?: any 8 | preventScrollReset?: boolean 9 | isActive: boolean 10 | } 11 | 12 | const NavLink = React.forwardRef((props, ref) => { 13 | const { isActive, children, ...rest } = props 14 | return ( 15 | 16 | {children} 17 | 18 | ) 19 | }) 20 | 21 | NavLink.displayName = 'NavLink' 22 | 23 | const anchorWatcher = new (class { 24 | anchors: HTMLAnchorElement[] = [] 25 | listeners: ((anchorVal: string) => void)[] = [] 26 | private listener: () => void 27 | 28 | constructor() { 29 | this.listener = throttle(this._matchActiveAnchor.bind(this), 200) 30 | } 31 | 32 | /** 33 | * get active anchor by position 34 | */ 35 | private _matchActiveAnchor() { 36 | // find the first element which close the top of viewport 37 | const closestElmIndex = this.anchors.findIndex( 38 | (elm, i) => elm.getBoundingClientRect().top > 128 || i === this.anchors.length - 1 39 | ) 40 | const currentElm = this.anchors[Math.max(0, closestElmIndex - 1)] 41 | const anchorVal = currentElm && currentElm.parentElement!.id 42 | // trigger listeners 43 | this.listeners.forEach((fn) => fn(anchorVal)) 44 | } 45 | 46 | /** 47 | * watch position for specific element 48 | * @param elm element 49 | */ 50 | watch(elm: HTMLAnchorElement) { 51 | if (this.anchors.length === 0 && typeof window !== 'undefined') { 52 | window.addEventListener('scroll', this.listener) 53 | } 54 | 55 | this.anchors.push(elm) 56 | // match immediately to get initial active anchor 57 | this.listener() 58 | } 59 | 60 | /** 61 | * unwatch position for specific element 62 | * @param elm element 63 | */ 64 | unwatch(elm: HTMLAnchorElement) { 65 | this.anchors.splice( 66 | this.anchors.findIndex((anchor) => anchor === elm), 67 | 1 68 | ) 69 | 70 | if (this.anchors.length === 0 && typeof window !== 'undefined') { 71 | window.removeEventListener('scroll', this.listener) 72 | } 73 | } 74 | 75 | /** 76 | * listen active anchor change 77 | * @param fn callback 78 | */ 79 | listen(fn: (anchorVal: string) => void) { 80 | this.listeners.push(fn) 81 | } 82 | 83 | /** 84 | * unlisten active anchor change 85 | * @param fn callback 86 | */ 87 | unlisten(fn: (anchorVal: string) => void) { 88 | this.listeners.splice( 89 | this.listeners.findIndex((f) => f === fn), 90 | 1 91 | ) 92 | } 93 | })() 94 | 95 | // @ts-ignore 96 | function getElmScrollPosition(elm: HTMLElement) { 97 | return ( 98 | elm.offsetTop + (elm.offsetParent ? getElmScrollPosition(elm.offsetParent as HTMLElement) : 0) 99 | ) 100 | } 101 | 102 | const AnchorLink: React.FC & { scrollToAnchor: (anchor: string) => void } = (props) => { 103 | const hash = ((props.to || props.href) as string).match(/(#[^&?]*)/)?.[1] || '' 104 | const ref = useRef(null) 105 | const [isActive, setIsActive] = useState(false) 106 | 107 | useEffect(() => { 108 | if ( 109 | // @ts-ignore 110 | ['H1', 'H2', 'H3'].includes(ref.current?.parentElement?.tagName) && 111 | ref.current?.parentElement?.id 112 | ) { 113 | // only listen anchors within content area, mark by tranformer/remark/link.ts 114 | const elm = ref.current 115 | 116 | // push element to list 117 | anchorWatcher.watch(elm) 118 | return () => { 119 | // release element from list 120 | anchorWatcher.unwatch(elm) 121 | } 122 | } 123 | 124 | // listen active anchor change for non-title anchor links 125 | const fn = (anchorVal: string) => { 126 | setIsActive(hash === `#${anchorVal}`) 127 | } 128 | 129 | anchorWatcher.listen(fn) 130 | 131 | return () => anchorWatcher.unlisten(fn) 132 | }, []) 133 | return ( 134 | AnchorLink.scrollToAnchor(hash.substring(1))} 138 | isActive={isActive} 139 | /> 140 | ) 141 | } 142 | 143 | AnchorLink.scrollToAnchor = (anchor: string) => { 144 | // wait for dom update 145 | window.requestAnimationFrame(() => { 146 | const elm = document.getElementById(decodeURIComponent(anchor)) 147 | 148 | if (elm) { 149 | // compatible in Edge 150 | window.scrollTo(0, getElmScrollPosition(elm) - 100) 151 | } 152 | }) 153 | } 154 | 155 | export default AnchorLink 156 | --------------------------------------------------------------------------------