├── .babelrc
├── .gitignore
├── README.md
├── config-overrides.js
├── docs
├── Javascript.png
└── cnode-preview-compress.png
├── package.json
├── public
├── favicon.ico
├── index.html
├── logo192.png
├── logo512.png
├── manifest.json
├── robots.txt
└── run_prettify.js
├── rename.sh
├── src
├── App.tsx
├── assets
│ └── logo.svg
├── components
│ ├── header
│ │ └── index.tsx
│ ├── image
│ │ ├── index.tsx
│ │ └── style.ts
│ ├── loading
│ │ └── index.tsx
│ ├── scroll-list
│ │ └── index.tsx
│ ├── tabbar
│ │ ├── index.tsx
│ │ └── style.ts
│ └── tag
│ │ └── index.tsx
├── global.d.ts
├── hooks
│ ├── useAsync.ts
│ ├── useCache.ts
│ ├── useInitPosition.ts
│ └── useLoadMore.ts
├── index.tsx
├── layouts
│ ├── base-layout
│ │ ├── index.tsx
│ │ └── style.ts
│ └── index.ts
├── react-app-env.d.ts
├── service
│ ├── base.ts
│ └── cnode-sdk.ts
├── serviceWorker.js
├── style
│ ├── common.ts
│ ├── constants.ts
│ └── global.ts
├── types
│ └── index.ts
├── utils
│ └── isEmpty.ts
└── view
│ ├── about
│ └── index.tsx
│ ├── article
│ ├── code-prettify-sunburst.css
│ ├── comment-panel
│ │ ├── index.tsx
│ │ └── style.tsx
│ ├── comment
│ │ ├── index.tsx
│ │ └── style.tsx
│ ├── index.tsx
│ ├── info-bar
│ │ ├── index.tsx
│ │ └── style.ts
│ └── style.tsx
│ ├── not-found
│ └── index.tsx
│ ├── topic
│ ├── card
│ │ ├── card.tsx
│ │ └── style.tsx
│ └── index.tsx
│ └── user
│ ├── index.tsx
│ └── style.tsx
├── tsconfig.json
└── yarn.lock
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": ["@babel/plugin-proposal-optional-chaining"]
3 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
3 |
4 | # dependencies
5 | /node_modules
6 | /.pnp
7 | .pnp.js
8 |
9 | # testing
10 | /coverage
11 |
12 | # production
13 | /build
14 |
15 | # misc
16 | .DS_Store
17 | .env.local
18 | .env.development.local
19 | .env.test.local
20 | .env.production.local
21 |
22 | npm-debug.log*
23 | yarn-debug.log*
24 | yarn-error.log*
25 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | #### Cnodejs based on react
2 |
3 | 此项目为复刻[cnode社区](https://cnodejs.org)制作的H5版本,采用react hooks全面改写,零外部UI组件依赖。可供入门react hooks进行研究。预览效果,可扫描文章下方的二维码。`PS:由于cnode服务器在海外,API请求延时有时会比较长。`
4 |
5 | #### Feature / 功能
6 |
7 | - [x] 骨架屏Loading动画(后续考虑使用自动化生成)
8 | - [x] 异步Hook`useAsync`
9 | - [x] 列表页无限滚动`useLoadMore`(采用`IntersationObserver`)
10 | - [ ] 列表页数据缓存策略
11 | - [x] markdown与代码高亮
12 |
13 | - [ ] 用户登录与保持
14 | - [ ] 点赞与收藏
15 |
16 | #### Design / 效果图
17 |
18 | 
19 |
20 | #### Preview / 预览
21 |
22 | 
--------------------------------------------------------------------------------
/config-overrides.js:
--------------------------------------------------------------------------------
1 | // const {
2 | // override,
3 | // addDecoratorsLegacy,
4 | // disableEsLint,
5 | // addWebpackAlias,
6 | // overrideDevServer
7 | // } = require("customize-cra")
8 | const path = require("path")
9 |
10 | module.exports = {
11 | webpack: config => {
12 | config.resolve.alias = {
13 | ...config.resolve.alias,
14 | '@': path.resolve(__dirname, 'src')
15 | }
16 | return config;
17 | }
18 | }
--------------------------------------------------------------------------------
/docs/Javascript.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/impeiran/react-cnodejs/9544aa3f7b210bb3b71c85bb5d842b3185620c61/docs/Javascript.png
--------------------------------------------------------------------------------
/docs/cnode-preview-compress.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/impeiran/react-cnodejs/9544aa3f7b210bb3b71c85bb5d842b3185620c61/docs/cnode-preview-compress.png
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-cnodejs",
3 | "homepage": "/cnode",
4 | "version": "0.1.0",
5 | "private": true,
6 | "dependencies": {
7 | "@testing-library/jest-dom": "^4.2.4",
8 | "@testing-library/react": "^9.3.2",
9 | "@testing-library/user-event": "^7.1.2",
10 | "@types/jest": "^25.2.1",
11 | "@types/node": "^13.13.2",
12 | "@types/react": "^16.9.34",
13 | "@types/react-dom": "^16.9.6",
14 | "@types/react-router-dom": "^5.1.4",
15 | "@types/styled-components": "^5.1.0",
16 | "axios": "^0.19.2",
17 | "github-markdown-css": "^4.0.0",
18 | "intersection-observer": "^0.7.0",
19 | "prop-types": "^15.7.2",
20 | "react": "^16.13.0",
21 | "react-content-loader": "^5.0.2",
22 | "react-dom": "^16.13.0",
23 | "react-redux": "^7.2.0",
24 | "react-router-config": "^5.1.1",
25 | "react-router-dom": "^5.1.2",
26 | "react-scripts": "3.4.1",
27 | "styled-components": "^5.0.1",
28 | "timeago.js": "^4.0.2",
29 | "typescript": "^3.8.3"
30 | },
31 | "scripts": {
32 | "start": "react-app-rewired start",
33 | "build": "react-app-rewired build",
34 | "test": "react-app-rewired test",
35 | "eject": "react-scripts eject"
36 | },
37 | "eslintConfig": {
38 | "extends": "react-app"
39 | },
40 | "browserslist": {
41 | "production": [
42 | ">0.2%",
43 | "not dead",
44 | "not op_mini all"
45 | ],
46 | "development": [
47 | "last 1 chrome version",
48 | "last 1 firefox version",
49 | "last 1 safari version"
50 | ]
51 | },
52 | "devDependencies": {
53 | "@babel/helper-validator-identifier": "^7.9.5",
54 | "@babel/plugin-proposal-optional-chaining": "^7.8.3",
55 | "customize-cra": "^0.9.1",
56 | "react-app-rewired": "^2.1.5"
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/impeiran/react-cnodejs/9544aa3f7b210bb3b71c85bb5d842b3185620c61/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
13 |
14 |
18 |
19 |
28 | cnodejs based on React
29 |
30 |
31 |
32 |
33 |
34 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/impeiran/react-cnodejs/9544aa3f7b210bb3b71c85bb5d842b3185620c61/public/logo192.png
--------------------------------------------------------------------------------
/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/impeiran/react-cnodejs/9544aa3f7b210bb3b71c85bb5d842b3185620c61/public/logo512.png
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/public/run_prettify.js:
--------------------------------------------------------------------------------
1 | !function(){/*
2 |
3 | Copyright (C) 2013 Google Inc.
4 |
5 | Licensed under the Apache License, Version 2.0 (the "License");
6 | you may not use this file except in compliance with the License.
7 | You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 |
17 | Copyright (C) 2006 Google Inc.
18 |
19 | Licensed under the Apache License, Version 2.0 (the "License");
20 | you may not use this file except in compliance with the License.
21 | You may obtain a copy of the License at
22 |
23 | http://www.apache.org/licenses/LICENSE-2.0
24 |
25 | Unless required by applicable law or agreed to in writing, software
26 | distributed under the License is distributed on an "AS IS" BASIS,
27 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
28 | See the License for the specific language governing permissions and
29 | limitations under the License.
30 | */
31 | (function(){function aa(g){function r(){try{L.doScroll("left")}catch(ba){k.setTimeout(r,50);return}x("poll")}function x(r){if("readystatechange"!=r.type||"complete"==z.readyState)("load"==r.type?k:z)[B](n+r.type,x,!1),!l&&(l=!0)&&g.call(k,r.type||r)}var X=z.addEventListener,l=!1,E=!0,v=X?"addEventListener":"attachEvent",B=X?"removeEventListener":"detachEvent",n=X?"":"on";if("complete"==z.readyState)g.call(k,"lazy");else{if(z.createEventObject&&L.doScroll){try{E=!k.frameElement}catch(ba){}E&&r()}z[v](n+
32 | "DOMContentLoaded",x,!1);z[v](n+"readystatechange",x,!1);k[v](n+"load",x,!1)}}function T(){U&&aa(function(){var g=M.length;ca(g?function(){for(var r=0;r=c?parseInt(e.substring(1),8):"u"===c||"x"===c?parseInt(e.substring(2),16):e.charCodeAt(1)}function f(e){if(32>e)return(16>e?"\\x0":"\\x")+e.toString(16);e=String.fromCharCode(e);
36 | return"\\"===e||"-"===e||"]"===e||"^"===e?"\\"+e:e}function c(e){var c=e.substring(1,e.length-1).match(RegExp("\\\\u[0-9A-Fa-f]{4}|\\\\x[0-9A-Fa-f]{2}|\\\\[0-3][0-7]{0,2}|\\\\[0-7]{1,2}|\\\\[\\s\\S]|-|[^-\\\\]","g"));e=[];var a="^"===c[0],b=["["];a&&b.push("^");for(var a=a?1:0,h=c.length;ap||122p||90p||122m[0]&&(m[1]+1>m[0]&&b.push("-"),b.push(f(m[1])));b.push("]");return b.join("")}function g(e){for(var a=e.source.match(RegExp("(?:\\[(?:[^\\x5C\\x5D]|\\\\[\\s\\S])*\\]|\\\\u[A-Fa-f0-9]{4}|\\\\x[A-Fa-f0-9]{2}|\\\\[0-9]+|\\\\[^ux0-9]|\\(\\?[:!=]|[\\(\\)\\^]|[^\\x5B\\x5C\\(\\)\\^]+)",
38 | "g")),b=a.length,d=[],h=0,m=0;h/,null])):d.push(["com",/^#[^\r\n]*/,null,"#"]));a.cStyleComments&&(f.push(["com",/^\/\/[^\r\n]*/,null]),f.push(["com",/^\/\*[\s\S]*?(?:\*\/|$)/,
45 | null]));if(c=a.regexLiterals){var g=(c=1|\\/=?|::?|<=?|>>?>?=?|,|;|\\?|@|\\[|~|{|\\^\\^?=?|\\|\\|?=?|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\\s*("+("/(?=[^/*"+c+"])(?:[^/\\x5B\\x5C"+c+"]|\\x5C"+g+"|\\x5B(?:[^\\x5C\\x5D"+c+"]|\\x5C"+g+")*(?:\\x5D|$))+/")+")")])}(c=a.types)&&f.push(["typ",c]);c=(""+a.keywords).replace(/^ | $/g,"");c.length&&f.push(["kwd",
46 | new RegExp("^(?:"+c.replace(/[\s,]+/g,"|")+")\\b"),null]);d.push(["pln",/^\s+/,null," \r\n\t\u00a0"]);c="^.[^\\s\\w.$@'\"`/\\\\]*";a.regexLiterals&&(c+="(?!s*/)");f.push(["lit",/^@[a-z_$][a-z_$@0-9]*/i,null],["typ",/^(?:[@_]?[A-Z]+[a-z][A-Za-z_$@0-9]*|\w+_t\b)/,null],["pln",/^[a-z_$][a-z_$@0-9]*/i,null],["lit",/^(?:0x[a-f0-9]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+\-]?\d+)?)[a-z]*/i,null,"0123456789"],["pln",/^\\[\s\S]?/,null],["pun",new RegExp(c),null]);return E(d,f)}function B(a,d,f){function c(a){var b=
47 | a.nodeType;if(1==b&&!r.test(a.className))if("br"===a.nodeName.toLowerCase())g(a),a.parentNode&&a.parentNode.removeChild(a);else for(a=a.firstChild;a;a=a.nextSibling)c(a);else if((3==b||4==b)&&f){var e=a.nodeValue,d=e.match(n);d&&(b=e.substring(0,d.index),a.nodeValue=b,(e=e.substring(d.index+d[0].length))&&a.parentNode.insertBefore(q.createTextNode(e),a.nextSibling),g(a),b||a.parentNode.removeChild(a))}}function g(a){function c(a,b){var e=b?a.cloneNode(!1):a,p=a.parentNode;if(p){var p=c(p,1),d=a.nextSibling;
48 | p.appendChild(e);for(var f=d;f;f=d)d=f.nextSibling,p.appendChild(f)}return e}for(;!a.nextSibling;)if(a=a.parentNode,!a)return;a=c(a.nextSibling,0);for(var e;(e=a.parentNode)&&1===e.nodeType;)a=e;b.push(a)}for(var r=/(?:^|\s)nocode(?:\s|$)/,n=/\r\n?|\n/,q=a.ownerDocument,k=q.createElement("li");a.firstChild;)k.appendChild(a.firstChild);for(var b=[k],t=0;t=+g[1],d=/\n/g,r=a.a,k=r.length,f=0,q=a.c,n=q.length,c=0,b=a.g,t=b.length,v=0;b[t]=k;var u,e;for(e=u=0;e=m&&(c+=2);f>=p&&(v+=2)}}finally{h&&(h.style.display=a)}}catch(y){Q.console&&console.log(y&&y.stack||y)}}var Q="undefined"!==typeof window?window:{},J=["break,continue,do,else,for,if,return,while"],K=[[J,"auto,case,char,const,default,double,enum,extern,float,goto,inline,int,long,register,restrict,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"],
52 | "catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"],R=[K,"alignas,alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,delegate,dynamic_cast,explicit,export,friend,generic,late_check,mutable,namespace,noexcept,noreturn,nullptr,property,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"],L=[K,"abstract,assert,boolean,byte,extends,finally,final,implements,import,instanceof,interface,null,native,package,strictfp,super,synchronized,throws,transient"],
53 | M=[K,"abstract,add,alias,as,ascending,async,await,base,bool,by,byte,checked,decimal,delegate,descending,dynamic,event,finally,fixed,foreach,from,get,global,group,implicit,in,interface,internal,into,is,join,let,lock,null,object,out,override,orderby,params,partial,readonly,ref,remove,sbyte,sealed,select,set,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,value,var,virtual,where,yield"],K=[K,"abstract,async,await,constructor,debugger,enum,eval,export,from,function,get,import,implements,instanceof,interface,let,null,of,set,undefined,var,with,yield,Infinity,NaN"],
54 | N=[J,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"],O=[J,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"],J=[J,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"],P=/^(DIR|FILE|array|vector|(de|priority_)?queue|(forward_)?list|stack|(const_)?(reverse_)?iterator|(unordered_)?(multi)?(set|map)|bitset|u?(int|float)\d*)\b/,
55 | S=/\S/,T=v({keywords:[R,M,L,K,"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END",N,O,J],hashComments:!0,cStyleComments:!0,multiLineStrings:!0,regexLiterals:!0}),V={};n(T,["default-code"]);n(E([],[["pln",/^[^]+/],["dec",/^]*(?:>|$)/],["com",/^<\!--[\s\S]*?(?:-\->|$)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],["pun",/^(?:<[%?]|[%?]>)/],["lang-",
56 | /^]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-js",/^