├── views ├── error.jade ├── listArticle.jade ├── gallary.jade ├── search.jade ├── advice.jade ├── broadchat.jade ├── login.jade ├── regist.jade ├── index.jade ├── editArticle.jade ├── writeArticle.jade ├── layout.jade ├── userDetail.jade ├── articleDetail.jade └── userCenter.jade ├── public ├── 404 │ ├── boat.png │ ├── cloud.png │ ├── wave-back.png │ ├── wave-front.png │ ├── 404.html │ └── 404.css ├── stylesheets │ ├── page │ │ ├── remind.css │ │ ├── userDetail.css │ │ ├── admire.css │ │ ├── user.css │ │ ├── bookmark.css │ │ ├── writeArticle.css │ │ ├── search.css │ │ ├── uploadPic.css │ │ ├── index.css │ │ ├── tag.css │ │ ├── articleDetail.css │ │ ├── listArticle.css │ │ ├── userCenter.css │ │ ├── comment.css │ │ └── gallary.css │ ├── pageTools │ │ ├── remind.css │ │ ├── user.css │ │ ├── article.css │ │ ├── admire.css │ │ ├── bookmark.css │ │ ├── tag.css │ │ └── comment.css │ ├── github.css │ ├── monokai.css │ └── style.css ├── gallary │ ├── 36.jpg │ ├── 未来.JPG │ ├── 无标题.jpg │ ├── 97897383_2.jpg │ ├── 1327390473_3dtj_4.jpg │ ├── ae8e9a18ab19d23f4bedbce2.jpg │ ├── large_nKru_2391000169cf1260.jpg │ ├── 0750f1a8bb3bb36bc359cb9c6703f396.jpg │ ├── 304be9c3a55a1b416cb91ada4875f0cb.jpg │ ├── 347784364248d78f55c50f82fed7dd77.jpg │ ├── 3622f6ee4bdcc69e40b2fd2a54562122.jpg │ ├── 83146ca0gw1e5uv98fxwcj20c80nfdhs.jpg │ ├── a3d96e7c85580ec6aef37b242a9ed451.jpg │ ├── original_5fSW_59ff000093b9118c.jpg │ ├── original_DVSi_2346000169651260.jpg │ ├── u=553037556,1932829573&fm=9&gp=0.jpg │ ├── 205cdf88d43f879491629c4bd21b0ef41ad53acb.jpg │ ├── 25c9c636afc37931e9ac3401e9c4b74542a9118f.jpg │ ├── 2fb27610b912c8fc769546e5fe039245d6882169.jpg │ ├── 472309f7905298227ea53ce9d5ca7bcb0a46d401.jpg │ ├── 57e853d9f2d3572c9c93a2f98813632762d0c35f.jpg │ ├── 70ccd1fe9925bc31a8c823b35cdf8db1cb137039.jpg │ ├── aa18972bd40735fab975fa929c510fb30f240817.jpg │ ├── bdc7a825bc315c60dd857dca8fb1cb13495477b8.jpg │ ├── d6ca7bcb0a46f21f78d01634f7246b600d33aef5.jpg │ ├── m2w690hq92lt_large_LE4J_0156000035c81260.jpg │ ├── m2w690hq92lt_large_xlCV_77500001a647125f.jpg │ ├── m2w690hq92lt_original_sKaq_414f00003ce9118c.jpg │ └── 科拉传奇.The.Legend.of.Korra.S01E12.Chi_Eng.WEB-HR.AAC.1024X576.x264-YYeTs&SLOMO[01-10-12].JPG ├── images │ ├── boat.png │ ├── cloud.png │ ├── favicon.ico │ ├── loading.gif │ ├── wave-back.png │ ├── wave-front.png │ └── default_avatar.jpg ├── gallary_sm │ ├── 36.jpg │ ├── 无标题.jpg │ ├── 未来.JPG │ ├── 97897383_2.jpg │ ├── 1327390473_3dtj_4.jpg │ ├── ae8e9a18ab19d23f4bedbce2.jpg │ ├── large_nKru_2391000169cf1260.jpg │ ├── original_5fSW_59ff000093b9118c.jpg │ ├── original_DVSi_2346000169651260.jpg │ ├── 0750f1a8bb3bb36bc359cb9c6703f396.jpg │ ├── 304be9c3a55a1b416cb91ada4875f0cb.jpg │ ├── 347784364248d78f55c50f82fed7dd77.jpg │ ├── 3622f6ee4bdcc69e40b2fd2a54562122.jpg │ ├── 83146ca0gw1e5uv98fxwcj20c80nfdhs.jpg │ ├── a3d96e7c85580ec6aef37b242a9ed451.jpg │ ├── u=553037556,1932829573&fm=9&gp=0.jpg │ ├── 205cdf88d43f879491629c4bd21b0ef41ad53acb.jpg │ ├── 25c9c636afc37931e9ac3401e9c4b74542a9118f.jpg │ ├── 2fb27610b912c8fc769546e5fe039245d6882169.jpg │ ├── 472309f7905298227ea53ce9d5ca7bcb0a46d401.jpg │ ├── 57e853d9f2d3572c9c93a2f98813632762d0c35f.jpg │ ├── 70ccd1fe9925bc31a8c823b35cdf8db1cb137039.jpg │ ├── aa18972bd40735fab975fa929c510fb30f240817.jpg │ ├── bdc7a825bc315c60dd857dca8fb1cb13495477b8.jpg │ ├── d6ca7bcb0a46f21f78d01634f7246b600d33aef5.jpg │ ├── m2w690hq92lt_large_LE4J_0156000035c81260.jpg │ ├── m2w690hq92lt_large_xlCV_77500001a647125f.jpg │ ├── m2w690hq92lt_original_sKaq_414f00003ce9118c.jpg │ └── 科拉传奇.The.Legend.of.Korra.S01E12.Chi_Eng.WEB-HR.AAC.1024X576.x264-YYeTs&SLOMO[01-10-12].JPG ├── fonts │ ├── glyphicons-halflings-regular.eot │ ├── glyphicons-halflings-regular.ttf │ └── glyphicons-halflings-regular.woff └── javascripts │ ├── page │ ├── listArticle_owner.js │ ├── login.js │ ├── index.js │ ├── global.js │ ├── regist.js │ ├── listArticle.js │ ├── userDetail.js │ ├── writeArticle.js │ ├── search.js │ ├── editArticle.js │ ├── uploadPic.js │ └── articleDetail.js │ ├── tools │ └── showdown_extensions │ │ ├── github.js │ │ ├── prettify.js │ │ ├── twitter.js │ │ └── table.js │ ├── pageTools │ ├── User.js │ └── Remind.js │ └── exp │ └── broadchat.js ├── routes ├── Actions │ ├── ExpAction.js │ ├── GallaryAction.js │ ├── RemindAction.js │ ├── TagAction.js │ ├── AdmireAction.js │ ├── BookmarkAction.js │ ├── UserAction.js │ ├── PictureAction.js │ ├── CommentAction.js │ └── ArticleAction.js ├── setting.js ├── Model │ ├── Tag.js │ ├── User.js │ ├── Admire.js │ ├── Comment.js │ ├── Bookmark.js │ ├── CommonDAO.js │ ├── Remind.js │ └── Article.js └── index.js ├── upload_tmp └── 5792-oxlaq1.jpg ├── package.json ├── .gitattributes ├── README.md ├── doc ├── home.md ├── user.md ├── commonDAO.md ├── bookmark.md └── comment.md ├── articles └── test.md └── .gitignore /views/error.jade: -------------------------------------------------------------------------------- 1 | #{message} -------------------------------------------------------------------------------- /public/stylesheets/page/remind.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/stylesheets/page/userDetail.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/404/boat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LingyuCoder/NodeBlog/HEAD/public/404/boat.png -------------------------------------------------------------------------------- /public/404/cloud.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LingyuCoder/NodeBlog/HEAD/public/404/cloud.png -------------------------------------------------------------------------------- /public/gallary/36.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LingyuCoder/NodeBlog/HEAD/public/gallary/36.jpg -------------------------------------------------------------------------------- /public/gallary/未来.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LingyuCoder/NodeBlog/HEAD/public/gallary/未来.JPG -------------------------------------------------------------------------------- /public/gallary/无标题.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LingyuCoder/NodeBlog/HEAD/public/gallary/无标题.jpg -------------------------------------------------------------------------------- /public/images/boat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LingyuCoder/NodeBlog/HEAD/public/images/boat.png -------------------------------------------------------------------------------- /public/images/cloud.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LingyuCoder/NodeBlog/HEAD/public/images/cloud.png -------------------------------------------------------------------------------- /public/404/wave-back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LingyuCoder/NodeBlog/HEAD/public/404/wave-back.png -------------------------------------------------------------------------------- /public/404/wave-front.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LingyuCoder/NodeBlog/HEAD/public/404/wave-front.png -------------------------------------------------------------------------------- /public/gallary_sm/36.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LingyuCoder/NodeBlog/HEAD/public/gallary_sm/36.jpg -------------------------------------------------------------------------------- /public/gallary_sm/无标题.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LingyuCoder/NodeBlog/HEAD/public/gallary_sm/无标题.jpg -------------------------------------------------------------------------------- /public/gallary_sm/未来.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LingyuCoder/NodeBlog/HEAD/public/gallary_sm/未来.JPG -------------------------------------------------------------------------------- /public/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LingyuCoder/NodeBlog/HEAD/public/images/favicon.ico -------------------------------------------------------------------------------- /public/images/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LingyuCoder/NodeBlog/HEAD/public/images/loading.gif -------------------------------------------------------------------------------- /routes/Actions/ExpAction.js: -------------------------------------------------------------------------------- 1 | exports.broadchat = function(req, res) { 2 | res.render("broadchat"); 3 | }; -------------------------------------------------------------------------------- /upload_tmp/5792-oxlaq1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LingyuCoder/NodeBlog/HEAD/upload_tmp/5792-oxlaq1.jpg -------------------------------------------------------------------------------- /public/images/wave-back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LingyuCoder/NodeBlog/HEAD/public/images/wave-back.png -------------------------------------------------------------------------------- /public/images/wave-front.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LingyuCoder/NodeBlog/HEAD/public/images/wave-front.png -------------------------------------------------------------------------------- /public/gallary/97897383_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LingyuCoder/NodeBlog/HEAD/public/gallary/97897383_2.jpg -------------------------------------------------------------------------------- /public/gallary_sm/97897383_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LingyuCoder/NodeBlog/HEAD/public/gallary_sm/97897383_2.jpg -------------------------------------------------------------------------------- /public/images/default_avatar.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LingyuCoder/NodeBlog/HEAD/public/images/default_avatar.jpg -------------------------------------------------------------------------------- /public/gallary/1327390473_3dtj_4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LingyuCoder/NodeBlog/HEAD/public/gallary/1327390473_3dtj_4.jpg -------------------------------------------------------------------------------- /public/gallary_sm/1327390473_3dtj_4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LingyuCoder/NodeBlog/HEAD/public/gallary_sm/1327390473_3dtj_4.jpg -------------------------------------------------------------------------------- /public/gallary/ae8e9a18ab19d23f4bedbce2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LingyuCoder/NodeBlog/HEAD/public/gallary/ae8e9a18ab19d23f4bedbce2.jpg -------------------------------------------------------------------------------- /public/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LingyuCoder/NodeBlog/HEAD/public/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /public/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LingyuCoder/NodeBlog/HEAD/public/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /public/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LingyuCoder/NodeBlog/HEAD/public/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /public/gallary/large_nKru_2391000169cf1260.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LingyuCoder/NodeBlog/HEAD/public/gallary/large_nKru_2391000169cf1260.jpg -------------------------------------------------------------------------------- /public/gallary_sm/ae8e9a18ab19d23f4bedbce2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LingyuCoder/NodeBlog/HEAD/public/gallary_sm/ae8e9a18ab19d23f4bedbce2.jpg -------------------------------------------------------------------------------- /public/gallary/0750f1a8bb3bb36bc359cb9c6703f396.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LingyuCoder/NodeBlog/HEAD/public/gallary/0750f1a8bb3bb36bc359cb9c6703f396.jpg -------------------------------------------------------------------------------- /public/gallary/304be9c3a55a1b416cb91ada4875f0cb.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LingyuCoder/NodeBlog/HEAD/public/gallary/304be9c3a55a1b416cb91ada4875f0cb.jpg -------------------------------------------------------------------------------- /public/gallary/347784364248d78f55c50f82fed7dd77.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LingyuCoder/NodeBlog/HEAD/public/gallary/347784364248d78f55c50f82fed7dd77.jpg -------------------------------------------------------------------------------- /public/gallary/3622f6ee4bdcc69e40b2fd2a54562122.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LingyuCoder/NodeBlog/HEAD/public/gallary/3622f6ee4bdcc69e40b2fd2a54562122.jpg -------------------------------------------------------------------------------- /public/gallary/83146ca0gw1e5uv98fxwcj20c80nfdhs.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LingyuCoder/NodeBlog/HEAD/public/gallary/83146ca0gw1e5uv98fxwcj20c80nfdhs.jpg -------------------------------------------------------------------------------- /public/gallary/a3d96e7c85580ec6aef37b242a9ed451.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LingyuCoder/NodeBlog/HEAD/public/gallary/a3d96e7c85580ec6aef37b242a9ed451.jpg -------------------------------------------------------------------------------- /public/gallary/original_5fSW_59ff000093b9118c.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LingyuCoder/NodeBlog/HEAD/public/gallary/original_5fSW_59ff000093b9118c.jpg -------------------------------------------------------------------------------- /public/gallary/original_DVSi_2346000169651260.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LingyuCoder/NodeBlog/HEAD/public/gallary/original_DVSi_2346000169651260.jpg -------------------------------------------------------------------------------- /public/gallary/u=553037556,1932829573&fm=9&gp=0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LingyuCoder/NodeBlog/HEAD/public/gallary/u=553037556,1932829573&fm=9&gp=0.jpg -------------------------------------------------------------------------------- /public/gallary_sm/large_nKru_2391000169cf1260.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LingyuCoder/NodeBlog/HEAD/public/gallary_sm/large_nKru_2391000169cf1260.jpg -------------------------------------------------------------------------------- /public/gallary_sm/original_5fSW_59ff000093b9118c.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LingyuCoder/NodeBlog/HEAD/public/gallary_sm/original_5fSW_59ff000093b9118c.jpg -------------------------------------------------------------------------------- /public/gallary_sm/original_DVSi_2346000169651260.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LingyuCoder/NodeBlog/HEAD/public/gallary_sm/original_DVSi_2346000169651260.jpg -------------------------------------------------------------------------------- /public/gallary_sm/0750f1a8bb3bb36bc359cb9c6703f396.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LingyuCoder/NodeBlog/HEAD/public/gallary_sm/0750f1a8bb3bb36bc359cb9c6703f396.jpg -------------------------------------------------------------------------------- /public/gallary_sm/304be9c3a55a1b416cb91ada4875f0cb.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LingyuCoder/NodeBlog/HEAD/public/gallary_sm/304be9c3a55a1b416cb91ada4875f0cb.jpg -------------------------------------------------------------------------------- /public/gallary_sm/347784364248d78f55c50f82fed7dd77.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LingyuCoder/NodeBlog/HEAD/public/gallary_sm/347784364248d78f55c50f82fed7dd77.jpg -------------------------------------------------------------------------------- /public/gallary_sm/3622f6ee4bdcc69e40b2fd2a54562122.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LingyuCoder/NodeBlog/HEAD/public/gallary_sm/3622f6ee4bdcc69e40b2fd2a54562122.jpg -------------------------------------------------------------------------------- /public/gallary_sm/83146ca0gw1e5uv98fxwcj20c80nfdhs.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LingyuCoder/NodeBlog/HEAD/public/gallary_sm/83146ca0gw1e5uv98fxwcj20c80nfdhs.jpg -------------------------------------------------------------------------------- /public/gallary_sm/a3d96e7c85580ec6aef37b242a9ed451.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LingyuCoder/NodeBlog/HEAD/public/gallary_sm/a3d96e7c85580ec6aef37b242a9ed451.jpg -------------------------------------------------------------------------------- /public/gallary_sm/u=553037556,1932829573&fm=9&gp=0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LingyuCoder/NodeBlog/HEAD/public/gallary_sm/u=553037556,1932829573&fm=9&gp=0.jpg -------------------------------------------------------------------------------- /public/gallary/205cdf88d43f879491629c4bd21b0ef41ad53acb.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LingyuCoder/NodeBlog/HEAD/public/gallary/205cdf88d43f879491629c4bd21b0ef41ad53acb.jpg -------------------------------------------------------------------------------- /public/gallary/25c9c636afc37931e9ac3401e9c4b74542a9118f.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LingyuCoder/NodeBlog/HEAD/public/gallary/25c9c636afc37931e9ac3401e9c4b74542a9118f.jpg -------------------------------------------------------------------------------- /public/gallary/2fb27610b912c8fc769546e5fe039245d6882169.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LingyuCoder/NodeBlog/HEAD/public/gallary/2fb27610b912c8fc769546e5fe039245d6882169.jpg -------------------------------------------------------------------------------- /public/gallary/472309f7905298227ea53ce9d5ca7bcb0a46d401.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LingyuCoder/NodeBlog/HEAD/public/gallary/472309f7905298227ea53ce9d5ca7bcb0a46d401.jpg -------------------------------------------------------------------------------- /public/gallary/57e853d9f2d3572c9c93a2f98813632762d0c35f.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LingyuCoder/NodeBlog/HEAD/public/gallary/57e853d9f2d3572c9c93a2f98813632762d0c35f.jpg -------------------------------------------------------------------------------- /public/gallary/70ccd1fe9925bc31a8c823b35cdf8db1cb137039.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LingyuCoder/NodeBlog/HEAD/public/gallary/70ccd1fe9925bc31a8c823b35cdf8db1cb137039.jpg -------------------------------------------------------------------------------- /public/gallary/aa18972bd40735fab975fa929c510fb30f240817.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LingyuCoder/NodeBlog/HEAD/public/gallary/aa18972bd40735fab975fa929c510fb30f240817.jpg -------------------------------------------------------------------------------- /public/gallary/bdc7a825bc315c60dd857dca8fb1cb13495477b8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LingyuCoder/NodeBlog/HEAD/public/gallary/bdc7a825bc315c60dd857dca8fb1cb13495477b8.jpg -------------------------------------------------------------------------------- /public/gallary/d6ca7bcb0a46f21f78d01634f7246b600d33aef5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LingyuCoder/NodeBlog/HEAD/public/gallary/d6ca7bcb0a46f21f78d01634f7246b600d33aef5.jpg -------------------------------------------------------------------------------- /public/gallary/m2w690hq92lt_large_LE4J_0156000035c81260.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LingyuCoder/NodeBlog/HEAD/public/gallary/m2w690hq92lt_large_LE4J_0156000035c81260.jpg -------------------------------------------------------------------------------- /public/gallary/m2w690hq92lt_large_xlCV_77500001a647125f.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LingyuCoder/NodeBlog/HEAD/public/gallary/m2w690hq92lt_large_xlCV_77500001a647125f.jpg -------------------------------------------------------------------------------- /public/gallary/m2w690hq92lt_original_sKaq_414f00003ce9118c.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LingyuCoder/NodeBlog/HEAD/public/gallary/m2w690hq92lt_original_sKaq_414f00003ce9118c.jpg -------------------------------------------------------------------------------- /public/gallary_sm/205cdf88d43f879491629c4bd21b0ef41ad53acb.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LingyuCoder/NodeBlog/HEAD/public/gallary_sm/205cdf88d43f879491629c4bd21b0ef41ad53acb.jpg -------------------------------------------------------------------------------- /public/gallary_sm/25c9c636afc37931e9ac3401e9c4b74542a9118f.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LingyuCoder/NodeBlog/HEAD/public/gallary_sm/25c9c636afc37931e9ac3401e9c4b74542a9118f.jpg -------------------------------------------------------------------------------- /public/gallary_sm/2fb27610b912c8fc769546e5fe039245d6882169.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LingyuCoder/NodeBlog/HEAD/public/gallary_sm/2fb27610b912c8fc769546e5fe039245d6882169.jpg -------------------------------------------------------------------------------- /public/gallary_sm/472309f7905298227ea53ce9d5ca7bcb0a46d401.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LingyuCoder/NodeBlog/HEAD/public/gallary_sm/472309f7905298227ea53ce9d5ca7bcb0a46d401.jpg -------------------------------------------------------------------------------- /public/gallary_sm/57e853d9f2d3572c9c93a2f98813632762d0c35f.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LingyuCoder/NodeBlog/HEAD/public/gallary_sm/57e853d9f2d3572c9c93a2f98813632762d0c35f.jpg -------------------------------------------------------------------------------- /public/gallary_sm/70ccd1fe9925bc31a8c823b35cdf8db1cb137039.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LingyuCoder/NodeBlog/HEAD/public/gallary_sm/70ccd1fe9925bc31a8c823b35cdf8db1cb137039.jpg -------------------------------------------------------------------------------- /public/gallary_sm/aa18972bd40735fab975fa929c510fb30f240817.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LingyuCoder/NodeBlog/HEAD/public/gallary_sm/aa18972bd40735fab975fa929c510fb30f240817.jpg -------------------------------------------------------------------------------- /public/gallary_sm/bdc7a825bc315c60dd857dca8fb1cb13495477b8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LingyuCoder/NodeBlog/HEAD/public/gallary_sm/bdc7a825bc315c60dd857dca8fb1cb13495477b8.jpg -------------------------------------------------------------------------------- /public/gallary_sm/d6ca7bcb0a46f21f78d01634f7246b600d33aef5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LingyuCoder/NodeBlog/HEAD/public/gallary_sm/d6ca7bcb0a46f21f78d01634f7246b600d33aef5.jpg -------------------------------------------------------------------------------- /public/gallary_sm/m2w690hq92lt_large_LE4J_0156000035c81260.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LingyuCoder/NodeBlog/HEAD/public/gallary_sm/m2w690hq92lt_large_LE4J_0156000035c81260.jpg -------------------------------------------------------------------------------- /public/gallary_sm/m2w690hq92lt_large_xlCV_77500001a647125f.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LingyuCoder/NodeBlog/HEAD/public/gallary_sm/m2w690hq92lt_large_xlCV_77500001a647125f.jpg -------------------------------------------------------------------------------- /public/gallary_sm/m2w690hq92lt_original_sKaq_414f00003ce9118c.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LingyuCoder/NodeBlog/HEAD/public/gallary_sm/m2w690hq92lt_original_sKaq_414f00003ce9118c.jpg -------------------------------------------------------------------------------- /public/stylesheets/pageTools/remind.css: -------------------------------------------------------------------------------- 1 | .b-remind-loading { 2 | background: url("/images/loading.gif") no-repeat; 3 | background-position: center; 4 | min-height: 32px; 5 | min-width: 32px 6 | } -------------------------------------------------------------------------------- /public/stylesheets/page/admire.css: -------------------------------------------------------------------------------- 1 | .admired{ 2 | background-color: #f0f0f0 3 | } 4 | 5 | .b-admire-loading { 6 | background: url("/images/loading.gif") no-repeat; 7 | background-position: center; 8 | min-height: 32px; 9 | min-width: 32px 10 | } -------------------------------------------------------------------------------- /public/gallary/科拉传奇.The.Legend.of.Korra.S01E12.Chi_Eng.WEB-HR.AAC.1024X576.x264-YYeTs&SLOMO[01-10-12].JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LingyuCoder/NodeBlog/HEAD/public/gallary/科拉传奇.The.Legend.of.Korra.S01E12.Chi_Eng.WEB-HR.AAC.1024X576.x264-YYeTs&SLOMO[01-10-12].JPG -------------------------------------------------------------------------------- /public/gallary_sm/科拉传奇.The.Legend.of.Korra.S01E12.Chi_Eng.WEB-HR.AAC.1024X576.x264-YYeTs&SLOMO[01-10-12].JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LingyuCoder/NodeBlog/HEAD/public/gallary_sm/科拉传奇.The.Legend.of.Korra.S01E12.Chi_Eng.WEB-HR.AAC.1024X576.x264-YYeTs&SLOMO[01-10-12].JPG -------------------------------------------------------------------------------- /public/stylesheets/page/user.css: -------------------------------------------------------------------------------- 1 | .b-user-loading { 2 | background: url("/images/loading.gif") no-repeat; 3 | background-position: center; 4 | min-height: 32px; 5 | min-width: 32px 6 | } 7 | 8 | .u-user-avatar img { 9 | height: 60px; 10 | width: 60px; 11 | border-radius: 30px; 12 | box-shadow: 0 0 6px #707070 13 | } 14 | 15 | .u-user-tags { 16 | width: 272px; 17 | padding-right: 20px; 18 | } -------------------------------------------------------------------------------- /public/stylesheets/pageTools/user.css: -------------------------------------------------------------------------------- 1 | .b-user-loading { 2 | background: url("/images/loading.gif") no-repeat; 3 | background-position: center; 4 | min-height: 32px; 5 | min-width: 32px 6 | } 7 | 8 | .u-user-avatar img { 9 | height: 60px; 10 | width: 60px; 11 | border-radius: 30px; 12 | box-shadow: 0 0 6px #707070 13 | } 14 | 15 | .u-user-tags { 16 | width: 272px; 17 | padding-right: 20px; 18 | } -------------------------------------------------------------------------------- /public/stylesheets/page/bookmark.css: -------------------------------------------------------------------------------- 1 | .b-bookmark-loading { 2 | background: url("/images/loading.gif") no-repeat; 3 | background-position: center; 4 | min-height: 32px; 5 | min-width: 32px 6 | } 7 | 8 | .u-bookmark { 9 | padding : 5px 10px; 10 | border-radius: 10px; 11 | cursor: pointer; 12 | } 13 | 14 | .b-bookmark-booked { 15 | background-color: #f0f0f0; 16 | } 17 | 18 | .b-bookmark-book:hover{ 19 | background-color: #f0f0f0; 20 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "myblog", 3 | "version": "0.0.1-31", 4 | "private": true, 5 | "scripts": { 6 | "start": "node app.js" 7 | }, 8 | "dependencies": { 9 | "express": "3.4.2", 10 | "jade": "*", 11 | "node-uuid": "1.4.1", 12 | "mongodb": "1.3.19", 13 | "markdown": "0.5.0", 14 | "async": "0.2.9", 15 | "moment": "2.4.0", 16 | "gm": "1.13.3", 17 | "ws": "0.4.31" 18 | }, 19 | "subdomain": "skyinlayer", 20 | "engines": { 21 | "node": "0.10.x" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /routes/setting.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | if(!fs.existsSync("upload_tmp")){ 3 | fs.mkdirSync("upload_tmp"); 4 | } 5 | 6 | if(!fs.existsSync("public/gallary_sm")){ 7 | fs.mkdirSync("public/gallary_sm"); 8 | } 9 | 10 | if(!fs.existsSync("public/gallary")){ 11 | fs.mkdirSync("public/gallary"); 12 | } 13 | 14 | module.exports = { 15 | //本地数据库测试 16 | host : process.env.DB || "mongodb://127.0.0.1:27017/myblog", 17 | gallary : { 18 | small : "gallary_sm", 19 | name : "gallary" 20 | }, 21 | uploadDir : "upload_tmp" 22 | }; -------------------------------------------------------------------------------- /public/javascripts/page/listArticle_owner.js: -------------------------------------------------------------------------------- 1 | (function($, window) { 2 | $(".u-delete").click(function(event) { 3 | var that = $(this); 4 | $.ajax({ 5 | url: "/nor/conf/article_remove", 6 | data: { 7 | articleId: that.attr("aid") 8 | }, 9 | dataType: "json", 10 | type: "get", 11 | }).done(function(data) { 12 | if(data.success){ 13 | that.parent().parent().remove(); 14 | } 15 | }).fail(function(err) { 16 | console.log(err.message); 17 | }); 18 | event.stopPropagation(); 19 | }); 20 | }(jQuery, window)); -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 天镶的博客 2 | ==== 3 | 一个基于Node.js开发的公共博客管理系统,使用express,mongodb进行开发 4 | 5 | 目前已部署到[nodejitsu](https://www.nodejitsu.com/)上,数据库使用[mongohq](http://www.mongohq.com/home),[单击访问主页](http://skyinlayer.nodejitsu.com/) 6 | 7 | ##目前的角色 8 | 9 | 1. 管理员 10 | 2. 来访用户 11 | 12 | ##目前的功能 13 | 14 | ###系统 15 | 16 | 1. 普通用户注册 17 | 2. 瀑布流查看博客图片 18 | 19 | ###所有用户 20 | 21 | 1. 查看/评论文章 22 | 2. 对评论点赞 23 | 3. 收藏文章 24 | 4. 查看用户中心 25 | 5. 修改基本的个人信息 26 | 27 | ###管理员额外功能 28 | 29 | 1. 添加/修改/删除 文章 30 | 2. 添加/删除 图片 31 | 32 | ##添加中的功能 33 | 34 | 1. 查看他人用户中心 35 | 2. 用户关注 36 | 3. 图片点赞 37 | 4. 文章访问量统计 38 | 5. ... 39 | 40 | -------------------------------------------------------------------------------- /public/javascripts/page/login.js: -------------------------------------------------------------------------------- 1 | (function($, window) { 2 | var formValidate = $("#formValidate"); 3 | $("#loginForm").submit(function(event){ 4 | formValidate.hide(); 5 | if (!this.username.value) { 6 | formValidate.text("请输入用户名").slideDown(); 7 | $(this.username).focus(); 8 | return false; 9 | } 10 | if (!this.password.value.match(/^[\w\-\u4e00-\u9fa5]{3,12}$/)) { 11 | formValidate.text("请输入密码").slideDown(); 12 | $(this.password).focus(); 13 | return false; 14 | } 15 | $(this).find("button[type='submit']").text("登陆中...").attr("disabled","disabled"); 16 | }); 17 | }(jQuery, window)); -------------------------------------------------------------------------------- /public/stylesheets/page/writeArticle.css: -------------------------------------------------------------------------------- 1 | .u-panel-body { 2 | margin: 0; 3 | padding: 0; 4 | position: relative; 5 | width: 47.5%; 6 | overflow: auto; 7 | height: 100%; 8 | } 9 | 10 | #leftPanel { 11 | float: left; 12 | left : 1.5%; 13 | } 14 | 15 | #rightPanel { 16 | float: right; 17 | right : 1.5%; 18 | padding: 2px; 19 | } 20 | 21 | #outputArticle { 22 | overflow: auto; 23 | } 24 | 25 | .u-pane { 26 | margin: 0; 27 | padding: 0; 28 | padding-left: 4px; 29 | width: 100%; 30 | border: none; 31 | display: block; 32 | border: 1px solid #888; 33 | border-right: 1px solid #000; 34 | border-bottom: 1px solid #000; 35 | height: 100%; 36 | } -------------------------------------------------------------------------------- /views/listArticle.jade: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | block style 4 | link(rel='stylesheet', href='/stylesheets/github.css') 5 | link(rel='stylesheet', href='/stylesheets/page/listArticle.css') 6 | block content 7 | div(class="container") 8 | div(class="g-arts") 9 | div(class="u-panel u-more") 向下滚动加载更多 10 | 11 | block javascript 12 | script(type='text/javascript', src='/javascripts/tools/highlight.pack.js') 13 | script(type='text/javascript', src='/javascripts/pageTools/User.js') 14 | script(type='text/javascript', src='/javascripts/pageTools/Article.js') 15 | script(type='text/javascript', src='/javascripts/page/listArticle.js') 16 | if user && user.owner 17 | script(type='text/javascript', src='/javascripts/page/listArticle_owner.js') -------------------------------------------------------------------------------- /public/javascripts/page/index.js: -------------------------------------------------------------------------------- 1 | (function($, window) { 2 | $('pre code').each(function(i, e) { 3 | hljs.highlightBlock(e); 4 | }); 5 | 6 | $(".m-tags").each(function() { 7 | var that = $(this), 8 | articleId = that.attr("aid"); 9 | $(document).trigger("tag.drawArticleTags", [articleId, that]); 10 | }); 11 | 12 | $(".u-avatar").each(function() { 13 | var that = $(this); 14 | $(document).trigger("user.draw", [that.attr("uid"), that]); 15 | }); 16 | 17 | $(".u-comment").each(function() { 18 | $(document).trigger("comment.drawCount", [$(this).attr("aid"), $(this)]); 19 | }); 20 | 21 | $(".u-book").each(function() { 22 | $(document).trigger("bookmark.draw", [$(this).attr("aid"), $(this), $(this).attr("cur")]); 23 | }); 24 | 25 | $(".g-art-body .u-panel").click(function(event){ 26 | window.location.href = "/article_load?articleId=" + $(this).attr("aid"); 27 | }); 28 | 29 | }(jQuery, window)); -------------------------------------------------------------------------------- /public/javascripts/tools/showdown_extensions/github.js: -------------------------------------------------------------------------------- 1 | // 2 | // Github Extension (WIP) 3 | // ~~strike-through~~ -> strike-through 4 | // 5 | 6 | (function(){ 7 | var github = function(converter) { 8 | return [ 9 | { 10 | // strike-through 11 | // NOTE: showdown already replaced "~" with "~T", so we need to adjust accordingly. 12 | type : 'lang', 13 | regex : '(~T){2}([^~]+)(~T){2}', 14 | replace : function(match, prefix, content, suffix) { 15 | return '' + content + ''; 16 | } 17 | } 18 | ]; 19 | }; 20 | 21 | // Client-side export 22 | if (typeof window !== 'undefined' && window.Showdown && window.Showdown.extensions) { window.Showdown.extensions.github = github; } 23 | // Server-side export 24 | if (typeof module !== 'undefined') module.exports = github; 25 | }()); 26 | -------------------------------------------------------------------------------- /public/javascripts/page/global.js: -------------------------------------------------------------------------------- 1 | (function($, window) { 2 | $("#hasRemind").each(function() { 3 | var that = $(this); 4 | $(document).trigger("remind.countAll", [ 5 | 6 | function(err, total) { 7 | if (err) { 8 | that.text("获取消息失败"); 9 | } 10 | var $reminds; 11 | if (total === 0) { 12 | that.text("没有新消息"); 13 | } else { 14 | that.append("" + total + "").append("新消息").append(""); 15 | $reminds = $("").insertAfter(that); 16 | $(document).trigger("remind.draw", [$reminds]); 17 | } 18 | } 19 | ]); 20 | }); 21 | $(document).scroll(function(event) { 22 | if ($(window).scrollTop() > 100) { 23 | $("#scrollToTop").fadeIn(); 24 | } else { 25 | $("#scrollToTop").fadeOut(); 26 | } 27 | }); 28 | $("#scrollToTop").click(function(event) { 29 | $('html,body').animate({ 30 | scrollTop: 0 31 | }, 'slow'); 32 | return false; 33 | }); 34 | }(jQuery, window)); -------------------------------------------------------------------------------- /public/javascripts/page/regist.js: -------------------------------------------------------------------------------- 1 | (function($, window) { 2 | var formValidate = $("#formValidate"); 3 | $("#registForm input:first").focus(); 4 | $("#registForm").submit(function(event) { 5 | formValidate.hide(); 6 | if (!this.username.value.match(/^[\w]{5,15}$/)) { 7 | formValidate.text("用户名必须为长度为5~15的字母、数字或下划线").slideDown(); 8 | $(this.username).focus(); 9 | return false; 10 | } 11 | if (!this.nickname.value.match(/^[\w\-\u4e00-\u9fa5]{3,12}$/)) { 12 | formValidate.text("昵称必须为长度为3~12的中文、字母、数字、下划线").slideDown(); 13 | $(this.nickname).focus(); 14 | return false; 15 | } 16 | if (!this.password.value.match(/^[a-zA-Z0-9]{5,15}$/)) { 17 | formValidate.text("密码必须为长度为5~15的字母或数字").slideDown(); 18 | $(this.password).focus(); 19 | return false; 20 | } 21 | if (this.password.value !== this.passAgain.value) { 22 | formValidate.text("两次输入密码不一致,请检查").slideDown(); 23 | $(this.password).focus(); 24 | return false; 25 | } 26 | $(this).find("button[type='submit']").text("注册中...").attr("disabled","disabled"); 27 | }); 28 | }(jQuery, window)); -------------------------------------------------------------------------------- /routes/Actions/GallaryAction.js: -------------------------------------------------------------------------------- 1 | var fs = require("fs"), 2 | gallary = require("../setting.js").gallary; 3 | 4 | exports.gallaryPage = function(req, res) { 5 | res.render("gallary"); 6 | }; 7 | 8 | exports.listByPage = function(req, res) { 9 | var curPage = req.query.curPage || 0, 10 | perPage = Number(req.query.perPage) || 10; 11 | path = "public/" + gallary.name; 12 | fs.readdir(path, function(err, files) { 13 | var i, 14 | start = curPage * perPage, 15 | end, 16 | total; 17 | if (err) return res.json(500, { 18 | message: err.message 19 | }); 20 | if (start > files.length) { 21 | res.json({ 22 | files: [], 23 | total: files.length, 24 | gallary: gallary.name, 25 | gallary_small: gallary.small 26 | }); 27 | } else { 28 | end = start + perPage > files.length ? files.length : start + perPage; 29 | res.json({ 30 | files: files.slice(start, end), 31 | total: files.length, 32 | start: start, 33 | end: end, 34 | gallary_small: gallary.small, 35 | gallary: gallary.name 36 | }); 37 | } 38 | }); 39 | }; -------------------------------------------------------------------------------- /views/gallary.jade: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | block style 4 | link(rel='stylesheet', href='/stylesheets/page/gallary.css') 5 | block content 6 | div(class="u-block", id="blocker") 7 | div(class="u-loading") 加载中,请等待 8 | div(class="u-fillPic", id="filler") 9 | div(class="u-opt u-close") 10 | span(class="glyphicon glyphicon-remove") 11 | center 12 | img 13 | div(class="container") 14 | div(id="gallary", class="row") 15 | div(class="col-sm-3 g-gal-col", index=0, id="col-0") 16 | div(class="col-sm-3 g-gal-col", index=1, id="col-1") 17 | div(class="col-sm-3 g-gal-col", index=2, id="col-2") 18 | div(class="col-sm-3 g-gal-col", index=3, id="col-3") 19 | div(class="clearfix") 20 | div(class="scrollFooter" ) 加载中,请稍后 21 | if user&&user.owner 22 | div(class="glyphicon glyphicon-trash u-conf-opt u-trash", id="trash") 23 | div(class="glyphicon glyphicon-upload u-conf-opt u-upload", id="upload") 24 | block javascript 25 | script(type='text/javascript', src='/javascripts/tools/jquery-mousewheel.js') 26 | script(type='text/javascript', src='/javascripts/page/gallary.js') -------------------------------------------------------------------------------- /public/javascripts/tools/showdown_extensions/prettify.js: -------------------------------------------------------------------------------- 1 | // 2 | // Google Prettify 3 | // A showdown extension to add Google Prettify (http://code.google.com/p/google-code-prettify/) 4 | // hints to showdown's HTML output. 5 | // 6 | 7 | (function(){ 8 | 9 | var prettify = function(converter) { 10 | return [ 11 | { type: 'output', filter: function(source){ 12 | 13 | return source.replace(/(
)?/gi, function(match, pre) {
14 |                     if (pre) {
15 |                         return '
';
16 |                     } else {
17 |                         return '';
18 |                     }
19 |                 });
20 |             }}
21 |         ];
22 |     };
23 | 
24 |     // Client-side export
25 |     if (typeof window !== 'undefined' && window.Showdown && window.Showdown.extensions) { window.Showdown.extensions.prettify = prettify; }
26 |     // Server-side export
27 |     if (typeof module !== 'undefined') module.exports = prettify;
28 | 
29 | }());
30 | 


--------------------------------------------------------------------------------
/public/404/404.html:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 | 	
 4 | 		啊哦,好像有什么东西找不到了
 5 | 		
 6 | 		
 7 | 	
 8 | 	
 9 | 		
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |

404

28 |

好像有什么东西找不到了

29 |
30 |
31 |
32 | 33 | -------------------------------------------------------------------------------- /views/search.jade: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | block style 4 | link(rel='stylesheet', href='/stylesheets/pageTools/tag.css') 5 | link(rel='stylesheet', href='/stylesheets/page/search.css') 6 | 7 | block content 8 | div(class="container") 9 | div(class="row", style="margin-top:40px") 10 | div(class="col-sm-8") 11 | div(class="g-arts u-panel") 12 | div(id="articles", curPage=0) 13 | div(class="u-more", id="more") 请根据条件进行搜索 14 | div(class="col-sm-4") 15 | div(class="g-tags u-panel") 16 | h3 标签搜索: 17 | hr 18 | div(id="tags") 19 | hr 20 | p 已选择标签: 21 | div(id="chosenTags") 22 | hr 23 | button(class="btn btn-success", id="tagSearchBtn") 给我搜 24 | div(class="g-tags u-panel") 25 | h3 标题搜索: 26 | hr 27 | input(class="form-control", type="text", id="titleSearchInput", placeholder="请输入部分标题") 28 | hr 29 | button(class="btn btn-success", id="titleSearchBtn") 给我搜 30 | 31 | block javascript 32 | script(type='text/javascript', src="/javascripts/pageTools/Tag.js") 33 | script(type='text/javascript', src="/javascripts/pageTools/Article.js") 34 | script(type='text/javascript', src="/javascripts/pageTools/User.js") 35 | script(type='text/javascript', src="/javascripts/page/search.js") 36 | 37 | -------------------------------------------------------------------------------- /public/stylesheets/page/search.css: -------------------------------------------------------------------------------- 1 | .u-more { 2 | width: 100%; 3 | background-color: #428bca; 4 | color:#fff; 5 | text-align: center; 6 | padding: 10px; 7 | border-radius: 20px; 8 | cursor: pointer; 9 | } 10 | 11 | .u-more:hover { 12 | background-color: #8f8f8f; 13 | color: #f0f0f0; 14 | } 15 | 16 | .g-row { 17 | margin: 10px 10px 20px 10px; 18 | padding: 10px; 19 | border: 2px dashed #333; 20 | border-radius: 10px; 21 | color: #333; 22 | cursor: pointer; 23 | } 24 | 25 | .g-row:after { 26 | display: table; 27 | content: ""; 28 | clear: both; 29 | } 30 | 31 | .g-row:hover { 32 | background: #f0f0f0 33 | } 34 | 35 | 36 | .g-row .u-avatar { 37 | float: left; 38 | width: 60px; 39 | height: 60px; 40 | } 41 | 42 | .g-row .u-avatar img { 43 | height: 100%; 44 | width: 100%; 45 | border-radius: 50%; 46 | box-shadow: 2px 2px 2px #333 47 | } 48 | 49 | .g-row .u-time { 50 | margin-left: 80px; 51 | width: calc(100% - 80px); 52 | 53 | text-align: right; 54 | font-weight: bolder; 55 | font-size: 120%; 56 | } 57 | 58 | .g-row .u-title { 59 | margin-left: 80px; 60 | margin-bottom: 24px; 61 | width: calc(100% - 80px); 62 | 63 | text-align: center; 64 | font-weight: bolder; 65 | font-size: 180%; 66 | } 67 | 68 | .u-panel { 69 | margin-top: 20px; 70 | } -------------------------------------------------------------------------------- /public/stylesheets/page/uploadPic.css: -------------------------------------------------------------------------------- 1 | .u-upload { 2 | width: 100%; 3 | border : 2px dashed #bbb; 4 | color : #bbb; 5 | -webkit-border-radius: 5px; 6 | -moz-border-radius: 5px; 7 | padding : 25px; 8 | margin : 50px 0; 9 | text-align: center; 10 | font: 20px bold 'Vollkorn'; 11 | } 12 | 13 | .uploadResult { 14 | width: 100%; 15 | height: 50px; 16 | display: none 17 | } 18 | 19 | .g-preview { 20 | position: relative; 21 | height: 300px; 22 | float: left; 23 | } 24 | 25 | .g-preview .u-process { 26 | position: absolute; 27 | width: 100%; 28 | height: 100%; 29 | line-height: 300px; 30 | text-align: center; 31 | font-size: 50px; 32 | text-overflow: clip; 33 | opacity: 0.7; 34 | text-shadow: 0 0 6px #f0f0f0; 35 | } 36 | 37 | .g-preview .u-image { 38 | height: 100% 39 | } 40 | 41 | .g-preview .u-remove { 42 | position: absolute; 43 | right : 0px; 44 | top : 0px; 45 | width: 36px; 46 | height: 36px; 47 | font-size: 20px; 48 | line-height: 36px; 49 | text-align: center; 50 | background-color: #f5f5f5; 51 | color : #818181; 52 | border: 1px solid #818181; 53 | border-radius: 18px; 54 | box-shadow: 3px 3px 8px #818181; 55 | } 56 | 57 | .g-preview .u-remove:hover { 58 | background-color: #818181; 59 | color : white; 60 | } 61 | 62 | .u-output { 63 | width: 100%; 64 | } -------------------------------------------------------------------------------- /views/advice.jade: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | block style 4 | 5 | block content 6 | div(class="container") 7 | div(class="jumbotron u-panel", style="margin-top:50px") 8 | h1 感谢您提供宝贵的意见 9 | p 如果您对这个博客有任何意见和建议,或者遇到了任何bug,均可在此进行提交 10 | p 如果您喜欢这个博客,请分享给你的好友: 11 | div(class="jiathis_style_32x32") 12 | a(class="jiathis_button_qzone") 13 | a(class="jiathis_button_tsina") 14 | a(class="jiathis_button_tqq") 15 | a(class="jiathis_button_renren") 16 | a(class="jiathis_button_kaixin001") 17 | a(href="http://www.jiathis.com/share", class="jiathis jiathis_txt jtico jtico_jiathis", target="_blank") 18 | a(class="jiathis_counter_style") 19 | div(class="clearfix") 20 | div(class = "ds-thread") 21 | 22 | block javascript 23 | script(type="text/javascript",src="http://v3.jiathis.com/code/jia.js",charset="utf-8") 24 | script(type = 'text/javascript') 25 | var duoshuoQuery = { 26 | short_name: "skyinlayer" 27 | }; 28 | (function() { 29 | var ds = document.createElement('script'); 30 | ds.type = 'text/javascript'; 31 | ds.async = true; 32 | ds.src = 'http://static.duoshuo.com/embed.js'; 33 | ds.charset = 'UTF-8'; 34 | (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(ds); 35 | })(); 36 | 37 | -------------------------------------------------------------------------------- /views/broadchat.jade: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | block style 4 | style(type="text/stylesheet") 5 | .g-row { 6 | width: 100%; 7 | margin-top:40px; 8 | } 9 | .g-row:after{ 10 | content:""; 11 | display: table; 12 | clear:both; 13 | } 14 | block content 15 | div(class="container") 16 | h2 17 | strong 版聊 18 | div(class="g-row", style="font-size:120%;font-weight: bolder") 19 | div(class="", style="width: 32%;float:right") 20 | div(class="u-panel", id="users") 版上的用户: 21 | hr 22 | div(class="u-panel", style="margin-top:20px", id="logs") 23 | div(class="u-panel", style="width: 66%;float:left") 24 | div(class="panel-body",nick=user?"#{user.nickname}":"游客", id="chat") 25 | div(class="form-group", style="padding: 20px 0;") 26 | input(type="text",class="form-control", style="width: 83%;float:left", id="message") 27 | button(class="btn btn-success", style="width: 15%;float:right", id="send") 发送 28 | div(class="form-group", style="padding: 20px 0;") 29 | input(type="text",class="form-control", style="width: 83%;float:left", id="nickname") 30 | button(class="btn btn-success", style="width: 15%;float:right", id="changeNick") 修改昵称 31 | 32 | 33 | block javascript 34 | script(type='text/javascript', src="/javascripts/exp/broadchat.js") 35 | 36 | -------------------------------------------------------------------------------- /doc/home.md: -------------------------------------------------------------------------------- 1 | 项目简介 2 | === 3 | *** 4 | ##简介 5 | > 一个多人共同管理的博客模板 6 | > 7 | > 使用nodejs和mongodb进行开发 8 | > 9 | > 采用了express框架和jade模板引擎 10 | 11 | ##效果 12 | > [点我跳转](http://skyinlayer.jit.su/) 13 | 14 | ##依赖 15 | > [express: 3.4.2](http://expressjs.com/) 16 | > 17 | > [jade: *](http://jade-lang.com/) 18 | > 19 | > [node-uuid: 1.4.1](http://github.com/broofa/node-uuid) 20 | > 21 | > [mongodb: 1.3.19](https://github.com/mongodb/node-mongodb-native) 22 | > 23 | > [markdown: 0.5.0](https://github.com/evilstreak/markdown-js) 24 | > 25 | > [async: 0.2.9](https://github.com/caolan/async) 26 | > 27 | > [moment: 2.4.0](http://momentjs.com/) 28 | > 29 | > [gm: 1.13.3](https://github.com/aheckmann/gm) 30 | 31 | ##文档 32 | 33 | ### 模型层 34 | > [公用数据库连接层](https://github.com/LingyuCoder/Blog/wiki/%E5%85%AC%E7%94%A8%E6%95%B0%E6%8D%AE%E5%BA%93%E8%BF%9E%E6%8E%A5%E5%B1%82-%5BCommonDAO.js%5D) 35 | 36 | > [文章](https://github.com/LingyuCoder/Blog/wiki/%E6%96%87%E7%AB%A0-%5BArticle%5D) 37 | 38 | > [用户](https://github.com/LingyuCoder/Blog/wiki/%E7%94%A8%E6%88%B7-%5BUser%5D) 39 | 40 | > [评论](https://github.com/LingyuCoder/Blog/wiki/%E8%AF%84%E8%AE%BA%5BComment%5D) 41 | 42 | > [书签](https://github.com/LingyuCoder/Blog/wiki/%E4%B9%A6%E7%AD%BE-%5BBookmark%5D) 43 | 44 | > [点赞(施工中)]() 45 | 46 | > [标签(施工中)]() 47 | 48 | ### 控制层(施工中) 49 | 50 | ### 表示层(施工中) -------------------------------------------------------------------------------- /views/login.jade: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | block content 4 | div(class='container') 5 | h1 6 | strong 登录 7 | if message 8 | div(class='alert alert-danger') #{message} 9 | div(class="alert alert-danger", id="formValidate", style="display:none") 10 | div(class="u-panel") 11 | div(class="panel-body") 12 | form(class="form-horizontal", method="post", action="/user_login", id="loginForm") 13 | div(class="form-group") 14 | label(for="inputUsername", class="col-sm-2 col-sm-offset-2 control-label") 用户名 15 | div(class="col-sm-5") 16 | input(type="text", name="username", class="form-control", id="inputUsername", placeholder="用户名") 17 | div(class="form-group") 18 | label(for="inputPassword", class="col-sm-2 col-sm-offset-2 control-label") 密码 19 | div(class="col-sm-5") 20 | input(type="password", name="password", class="form-control", id="inputPassword", placeholder="填写密码") 21 | div(class="form-group") 22 | button(type='submit', id='loginBtn', class='btn btn-success col-sm-1 col-sm-offset-4') 登录 23 | a(href="user_registPage", class="btn btn-primary col-sm-1 col-sm-offset-2") 注册 24 | 25 | block javascript 26 | script(type='text/javascript', src='/javascripts/page/login.js') -------------------------------------------------------------------------------- /articles/test.md: -------------------------------------------------------------------------------- 1 | ## ZOJ 1091 Knight Moves ## 2 | >**题意**:骑士遍历,给源点、目标点,求源点到目标点的最短步数 3 | > 4 | >**解法**:经典bfs 5 | > 6 | >**吐槽** :一直想用双向bfs写一次,不过这两天各种屁事。这题完全就是C++版本的翻译...熟悉了一下python的队列实现和矩阵,还是挺有收获的,发现我特别依赖字典,这样不好不好 7 | 8 | 9 | import sys 10 | des=[[2,1],[2,-1],[-2,1],[-2,-1],[1,2],[1,-2],[-1,2],[-1,-2]] 11 | def bfs(start,end): 12 | q=[] 13 | flag = [([0] * 11) for i in range(11)] 14 | q.append(start) 15 | flag[start['x']][start['y']]=1 16 | while len(q): 17 | cur=q[0] 18 | q=q[1:] 19 | if cur['x']==end['x'] and cur['y']==end['y']: 20 | return cur 21 | for i in range(len(des)): 22 | curx=des[i][0]+cur['x'] 23 | cury=des[i][1]+cur['y'] 24 | if curx<=8 and curx>=1 and cury<=8 and cury>=1 and flag[curx][cury]==0: 25 | flag[curx][cury]=1 26 | q.append({'x':curx,'y':cury,'t':cur['t']+1}) 27 | 28 | for line in sys.stdin: 29 | a=line.rstrip().split() 30 | start={'x':ord(a[0][0])-ord('a')+1,'y':ord(a[0][1])-ord('0'),'t':0} 31 | end={'x':ord(a[1][0])-ord('a')+1,'y':ord(a[1][1])-ord('0'),'t':0} 32 | ans=bfs(start,end) 33 | print 'To get from %s to %s takes %d knight moves.' %(a[0],a[1],ans['t']) -------------------------------------------------------------------------------- /public/stylesheets/pageTools/article.css: -------------------------------------------------------------------------------- 1 | .b-article-loading { 2 | background: url("/images/loading.gif") no-repeat; 3 | background-position: center; 4 | min-height: 32px; 5 | min-width: 32px 6 | } 7 | 8 | .g-article-row { 9 | width: 100%; 10 | font-size: 14px; 11 | } 12 | 13 | .g-article-row .g-info { 14 | display: block; 15 | height: 30px; 16 | margin-left : 100px; 17 | line-height: 30px; 18 | } 19 | 20 | .g-article-row .g-info .u-nick{ 21 | margin-right: 20px; 22 | font-weight : bolder; 23 | font-size: large 24 | } 25 | 26 | .g-article-row .g-info .u-time{ 27 | float:right; 28 | } 29 | 30 | .g-article-row .u-title { 31 | display: block; 32 | margin-left : 100px; 33 | margin-top : 10px; 34 | font-size: 150%; 35 | font-weight: bolder; 36 | text-align: center; 37 | margin-bottom: 10px 38 | } 39 | 40 | .g-article-row .u-avatar { 41 | float: left; 42 | } 43 | 44 | .g-article-row .g-operate { 45 | margin-left: 100px; 46 | height: 25px; 47 | } 48 | 49 | .g-article-row .g-operate:after { 50 | display: table; 51 | content: ''; 52 | clear: both; 53 | } 54 | 55 | .g-article-row .u-opt { 56 | float:right; 57 | margin-left : 10px; 58 | line-height: 14px; 59 | padding : 4px 6px; 60 | border-radius: 4px; 61 | cursor : pointer; 62 | } 63 | 64 | .g-article-row .u-opt:hover { 65 | background-color: #f0f0f0; 66 | } 67 | 68 | .g-article-row .u-opt{ 69 | text-decoration: none; 70 | color:#333; 71 | } -------------------------------------------------------------------------------- /routes/Model/Tag.js: -------------------------------------------------------------------------------- 1 | var commonDao = require("./CommonDAO.js"), 2 | collectionName = "tag", 3 | uuid = require("node-uuid"), 4 | __resultToListFn = function(callback) { 5 | return function(err, results) { 6 | var i; 7 | if (err) return callback(err); 8 | for (i = results.length; i--;) { 9 | results[i] = new Tag(results[i]); 10 | } 11 | callback(err, results); 12 | 13 | }; 14 | }; 15 | 16 | function Tag(tag) { 17 | this.name = tag.name; 18 | this.createTime = tag.createTime; 19 | this.id = tag.id; 20 | this.color = tag.color; 21 | } 22 | 23 | module.exports = Tag; 24 | 25 | Tag.prototype.save = function(callback) { 26 | commonDao.save(collectionName, { 27 | name: this.name, 28 | color: this.color, 29 | createTime: new Date().getTime(), 30 | id: uuid.v4() 31 | }, function(err, result) { 32 | if (err) return callback(err); 33 | if (!result[0]) return new Error("保存标签失败"); 34 | return callback(err, new Tag(result[0])); 35 | }); 36 | }; 37 | 38 | Tag.get = function(id, callback) { 39 | commonDao.findOne(collectionName, { 40 | id: id 41 | }, function(err, result) { 42 | if (err) return callback(err); 43 | callback(err, result ? new Tag(result) : result); 44 | }); 45 | }; 46 | 47 | Tag.getAll = function(callback) { 48 | commonDao.find(collectionName, { 49 | sort: { 50 | createTime: -1 51 | } 52 | }, __resultToListFn(callback)); 53 | }; 54 | 55 | Tag.getByFuzzyName = function(name, callback) { 56 | commonDao.find(collectionName, { 57 | condition: { 58 | name: new RegExp(name) 59 | }, 60 | sort: { 61 | createTime: -1 62 | } 63 | }, __resultToListFn(callback)); 64 | }; -------------------------------------------------------------------------------- /public/stylesheets/pageTools/admire.css: -------------------------------------------------------------------------------- 1 | .admired{ 2 | background-color: #f0f0f0 3 | } 4 | 5 | .b-admire-loading { 6 | background: url("/images/loading.gif") no-repeat; 7 | background-position: center; 8 | min-height: 32px; 9 | min-width: 32px 10 | } 11 | 12 | .g-admire-row { 13 | width: 100%; 14 | font-size: 14px; 15 | } 16 | 17 | .g-admire-row .g-info { 18 | display: block; 19 | height: 30px; 20 | margin-left : 100px; 21 | line-height: 30px; 22 | } 23 | 24 | .g-admire-row .g-info:after { 25 | display: table; 26 | clear: both; 27 | content: ""; 28 | } 29 | 30 | .g-admire-row .g-info .u-nick{ 31 | float: left; 32 | width: 50%; 33 | font-weight : bolder; 34 | font-size: large 35 | } 36 | 37 | .g-admire-row .g-info .u-time{ 38 | float:right; 39 | width: 50%; 40 | text-align: right 41 | } 42 | 43 | .g-admire-row .u-comment { 44 | display: block; 45 | margin-left : 100px; 46 | margin-top : 10px; 47 | font-size: large; 48 | border: 2px dashed #8F8F8F; 49 | border-radius: 20px; 50 | padding: 10px; 51 | margin-bottom: 10px 52 | } 53 | 54 | .g-admire-row .u-avatar { 55 | float: left; 56 | } 57 | 58 | .g-admire-row .g-operate { 59 | margin-left: 100px; 60 | height: 25px; 61 | } 62 | 63 | .g-admire-row .g-operate:after { 64 | display: table; 65 | content: ''; 66 | clear: both; 67 | } 68 | 69 | .g-admire-row .u-opt { 70 | float:right; 71 | margin-left : 10px; 72 | line-height: 14px; 73 | padding : 4px 6px; 74 | border-radius: 4px; 75 | cursor : pointer; 76 | } 77 | 78 | .g-admire-row .u-opt:hover { 79 | background-color: #f0f0f0; 80 | } 81 | 82 | .g-admire-row .u-opt{ 83 | text-decoration: none; 84 | color:#333; 85 | } 86 | -------------------------------------------------------------------------------- /public/javascripts/tools/showdown_extensions/twitter.js: -------------------------------------------------------------------------------- 1 | // 2 | // Twitter Extension 3 | // @username -> @username 4 | // #hashtag -> #hashtag 5 | // 6 | 7 | (function(){ 8 | 9 | var twitter = function(converter) { 10 | return [ 11 | 12 | // @username syntax 13 | { type: 'lang', regex: '\\B(\\\\)?@([\\S]+)\\b', replace: function(match, leadingSlash, username) { 14 | // Check if we matched the leading \ and return nothing changed if so 15 | if (leadingSlash === '\\') { 16 | return match; 17 | } else { 18 | return '@' + username + ''; 19 | } 20 | }}, 21 | 22 | // #hashtag syntax 23 | { type: 'lang', regex: '\\B(\\\\)?#([\\S]+)\\b', replace: function(match, leadingSlash, tag) { 24 | // Check if we matched the leading \ and return nothing changed if so 25 | if (leadingSlash === '\\') { 26 | return match; 27 | } else { 28 | return '#' + tag + ''; 29 | } 30 | }}, 31 | 32 | // Escaped @'s 33 | { type: 'lang', regex: '\\\\@', replace: '@' } 34 | ]; 35 | }; 36 | 37 | // Client-side export 38 | if (typeof window !== 'undefined' && window.Showdown && window.Showdown.extensions) { window.Showdown.extensions.twitter = twitter; } 39 | // Server-side export 40 | if (typeof module !== 'undefined') module.exports = twitter; 41 | 42 | }()); 43 | -------------------------------------------------------------------------------- /public/stylesheets/page/index.css: -------------------------------------------------------------------------------- 1 | .u-flag .u-flag-left a{ 2 | color : white; 3 | text-decoration: none; 4 | } 5 | 6 | .u-flag .u-flag-left a:hover{ 7 | color : #f0f0f0; 8 | } 9 | 10 | .u-flag .u-flag-left a:active{ 11 | color : gray; 12 | } 13 | 14 | .g-art-footer .m-tags { 15 | float: left; 16 | width: 60%; 17 | } 18 | 19 | .g-art-footer .u-item { 20 | float:right; 21 | padding : 5px 10px; 22 | border-radius: 10px; 23 | } 24 | 25 | .g-art-footer .u-item span{ 26 | margin-left: 5px; 27 | } 28 | 29 | .g-art-footer .u-item:hover{ 30 | background-color: #f0f0f0 31 | } 32 | 33 | .g-art-footer .u-booked { 34 | background-color: #f0f0f0 35 | } 36 | 37 | .g-art-info { 38 | height:200px; 39 | float:left; 40 | width: 20%;position:relative 41 | } 42 | 43 | .g-art-info .u-avatar { 44 | position: absolute; 45 | right: 20px; 46 | top: 45px; 47 | } 48 | 49 | .g-art-info .u-time { 50 | position: absolute; 51 | width: 100px; 52 | right: 20px; 53 | top: 120px; 54 | font-weight: bolder; 55 | text-align: right; 56 | } 57 | 58 | .g-art-body { 59 | float:left; 60 | width: 80%; 61 | position:relative; 62 | border-left: 3px solid #757575; 63 | } 64 | 65 | .g-art-body .u-panel { 66 | margin-top: 20px; 67 | min-height:200px; 68 | margin-left: 20px; 69 | cursor: pointer; 70 | } 71 | 72 | .g-art-body .u-panel:before { 73 | content: ""; 74 | width: 20px; 75 | height: 20px; 76 | border-top: 20px solid transparent; 77 | border-right: 20px solid rgba(255,255,255,0.8); 78 | border-bottom: 20px solid transparent; 79 | position: absolute; 80 | left: 0px; 81 | top: 55px; 82 | } 83 | 84 | .g-art-body .u-dot { 85 | position: absolute; 86 | width: 12px; 87 | height: 12px; 88 | border-radius: 50%; 89 | background-color: #757575; 90 | left: -7px; 91 | top: 70px; 92 | } -------------------------------------------------------------------------------- /public/javascripts/pageTools/User.js: -------------------------------------------------------------------------------- 1 | (function($, window) { 2 | var emitter = $(document); 3 | emitter.bind({ 4 | "user.draw": function(event, username, container, fnCallback) { 5 | container.addClass("b-user-loading"); 6 | emitter.trigger("user.getInfo", [username, 7 | function(err, user) { 8 | if (err) { 9 | if (typeof fnCallback === "function") fnCallback(err); 10 | return; 11 | } 12 | var $a, 13 | $img, 14 | $tags; 15 | container = container; 16 | $a = $(""); 17 | $img = $(""); 18 | $a.append($img); 19 | container.append($a); 20 | $tags = $("
"); 21 | $img.popover({ 22 | title: user.nickname, 23 | placement: "bottom", 24 | html: true, 25 | content: $tags 26 | }).hover(function(event) { 27 | $img.popover("show"); 28 | }, function(event) { 29 | $img.popover("hide"); 30 | }).bind("show.bs.popover", function() { 31 | $tags.html(""); 32 | $(document).trigger("tag.drawUserTags", [user.username, $tags]); 33 | }); 34 | container.removeClass("b-user-loading").data("user", user); 35 | if (typeof fnCallback === "function") fnCallback(null, container); 36 | } 37 | ]); 38 | }, 39 | "user.getInfo": function(event, username, fnCallback) { 40 | $.ajax({ 41 | url: "/user_getDetail", 42 | data: { 43 | username: username 44 | }, 45 | type: "post", 46 | dataType: "json" 47 | }).done(function(data) { 48 | if (typeof fnCallback === "function") fnCallback(null, data); 49 | }).fail(function(err) { 50 | if (typeof fnCallback === "function") fnCallback(err); 51 | }); 52 | } 53 | }); 54 | }(jQuery, window)); -------------------------------------------------------------------------------- /public/stylesheets/pageTools/bookmark.css: -------------------------------------------------------------------------------- 1 | .b-bookmark-loading { 2 | background: url("/images/loading.gif") no-repeat; 3 | background-position: center; 4 | min-height: 32px; 5 | min-width: 32px 6 | } 7 | 8 | .u-bookmark { 9 | padding : 5px 10px; 10 | border-radius: 10px; 11 | cursor: pointer; 12 | } 13 | 14 | .b-bookmark-booked, .b-bookmark-book:hover { 15 | background-color: rgba(66, 139, 202, 0.7); 16 | color: white; 17 | } 18 | 19 | .g-bookmark-row { 20 | width: 100%; 21 | font-size: 14px; 22 | } 23 | 24 | .g-bookmark-row .g-info { 25 | display: block; 26 | height: 30px; 27 | margin-left : 100px; 28 | line-height: 30px; 29 | } 30 | 31 | .g-bookmark-row .g-info:after { 32 | display: table; 33 | clear: both; 34 | content: ""; 35 | } 36 | 37 | .g-bookmark-row .g-info .u-nick{ 38 | float: left; 39 | width: 50%; 40 | font-weight : bolder; 41 | font-size: large 42 | } 43 | 44 | .g-bookmark-row .g-info .u-time{ 45 | float:right; 46 | width: 50%; 47 | text-align: right 48 | } 49 | 50 | .g-bookmark-row .u-comment { 51 | display: block; 52 | margin-left : 100px; 53 | margin-top : 10px; 54 | font-size: large; 55 | border: 2px dashed #8F8F8F; 56 | border-radius: 20px; 57 | padding: 10px; 58 | margin-bottom: 10px 59 | } 60 | 61 | .g-bookmark-row .u-avatar { 62 | float: left; 63 | } 64 | 65 | .g-bookmark-row .g-operate { 66 | margin-left: 100px; 67 | height: 25px; 68 | } 69 | 70 | .g-bookmark-row .g-operate:after { 71 | display: table; 72 | content: ''; 73 | clear: both; 74 | } 75 | 76 | .g-bookmark-row .u-opt { 77 | float:right; 78 | margin-left : 10px; 79 | line-height: 14px; 80 | padding : 4px 6px; 81 | border-radius: 4px; 82 | cursor : pointer; 83 | } 84 | 85 | .g-bookmark-row .u-opt:hover { 86 | background-color: #f0f0f0; 87 | } 88 | 89 | .g-bookmark-row .u-opt{ 90 | text-decoration: none; 91 | color:#333; 92 | } -------------------------------------------------------------------------------- /views/regist.jade: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | block content 4 | div(class='container') 5 | h1 6 | strong 注册 7 | if message 8 | div(class="alert alert-danger") #{message} 9 | div(class="alert alert-danger", id="formValidate", style="display:none") 10 | div(class="u-panel") 11 | div(class="panel-body") 12 | form(class="form-horizontal", method="post", action="/user_regist", id="registForm") 13 | div(class="form-group") 14 | label(for="inputUsername", class="col-sm-2 col-sm-offset-2 control-label") 用户名 15 | div(class="col-sm-5") 16 | input(type="text", name="username", class="form-control", id="inputUsername", placeholder="用户名") 17 | div(class="form-group") 18 | label(for="inputNickname", class="col-sm-2 col-sm-offset-2 control-label") 昵称 19 | div(class="col-sm-5") 20 | input(type="text", name="nickname", class="form-control", id="inputNickname", placeholder="昵称") 21 | div(class="form-group") 22 | label(for="inputPassword", class="col-sm-2 col-sm-offset-2 control-label") 密码 23 | div(class="col-sm-5") 24 | input(type="password", name="password", class="form-control", id="inputPassword", placeholder="填写密码") 25 | div(class="form-group") 26 | label(for="inputPassAgain", class="col-sm-2 col-sm-offset-2 control-label") 密码重复 27 | div(class="col-sm-5") 28 | input(type="password", name="passAgain", class="form-control", id="inputPassAgain", placeholder="再次填写密码") 29 | div(class="form-group") 30 | button(type="submit" class="col-sm-1 col-sm-offset-4 btn btn-success") 注册 31 | a(class="col-sm-offset-2 col-sm-1 btn btn-primary", href="user_loginPage") 登录 32 | 33 | block javascript 34 | script(type='text/javascript', src='/javascripts/page/regist.js') 35 | 36 | -------------------------------------------------------------------------------- /views/index.jade: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | block style 4 | link(rel='stylesheet', href='/stylesheets/github.css') 5 | link(rel='stylesheet', href='/stylesheets/pageTools/tag.css') 6 | link(rel='stylesheet', href='/stylesheets/pageTools/user.css') 7 | link(rel='stylesheet', href='/stylesheets/pageTools/comment.css') 8 | link(rel='stylesheet', href='/stylesheets/pageTools/bookmark.css') 9 | link(rel='stylesheet', href='/stylesheets/page/index.css') 10 | block content 11 | div(class='container', style="padding-top: 20px") 12 | each article in articles 13 | div(class="row" style="") 14 | div(class='g-art-info') 15 | div(class="u-avatar", uid="#{article.writer}") 16 | div(class="u-time") #{article.writeTime} 17 | 18 | div(class='g-art-body') 19 | div(class="u-dot") 20 | div(class="u-panel", aid="#{article.id}") 21 | div(class="u-title") 22 | h3 #{article.title} 23 | hr 24 | div 25 | !=article.content 26 | hr 27 | div(class="g-art-footer") 28 | div(class="m-tags", aid="#{article.id}") 29 | div(class="u-item u-book", aid="#{article.id}", cur=user?"#{user.username}":"") 30 | div(class="u-item u-comment", aid="#{article.id}") 31 | div(class="clearfix") 32 | ul(class="pager") 33 | li(class=curPage===1?"previous disabled" : "previous") 34 | a(href=curPage===1?"javascript:void(0)":"/index?page=#{curPage-1}") 35 | ←上一页 36 | li(class=curPage===totalPage?"next disabled" : "next") 37 | a(href=curPage===totalPage?"javascript:void(0)":"/index?page=#{curPage+1}") 38 | →下一页 39 | 40 | block javascript 41 | script(type='text/javascript', src='/javascripts/tools/highlight.pack.js') 42 | script(type='text/javascript', src='/javascripts/pageTools/Tag.js') 43 | script(type='text/javascript', src='/javascripts/pageTools/User.js') 44 | script(type='text/javascript', src='/javascripts/pageTools/Comment.js') 45 | script(type='text/javascript', src='/javascripts/pageTools/Bookmark.js') 46 | script(type='text/javascript', src='/javascripts/page/index.js') -------------------------------------------------------------------------------- /routes/Actions/RemindAction.js: -------------------------------------------------------------------------------- 1 | var Remind = require("../Model/Remind.js"), 2 | moment = require("moment"), 3 | async = require("async"); 4 | 5 | exports.getAll = function(req, res) { 6 | Remind.getByUser(req.session.user.username, Number(req.body.curPage), Number(req.body.perPage), function(err, reminds) { 7 | if (err) res.json(500, { 8 | message: err.message 9 | }); 10 | res.json({ 11 | reminds: reminds 12 | }); 13 | }); 14 | }; 15 | 16 | exports.getByType = function(req, res) { 17 | Remind.getByUserAndType(req.session.user.username, req.body.type, Number(req.body.curPage), Number(req.body.perPage), function(err, reminds) { 18 | if (err) res.json(500, { 19 | message: err.message 20 | }); 21 | res.json({ 22 | reminds: reminds 23 | }); 24 | }); 25 | }; 26 | 27 | exports.countUnreadAll = function(req, res) { 28 | Remind.countUnreadByUser(req.session.user.username, function(err, total) { 29 | if (err) res.json(500, { 30 | message: err.message 31 | }); 32 | res.json({ 33 | total: total 34 | }); 35 | }); 36 | }; 37 | 38 | exports.countUnreadByType = function(req, res) { 39 | Remind.countUnreadByUserAndType(req.session.user.username, req.body.type, function(err, total) { 40 | if (err) res.json(500, { 41 | message: err.message 42 | }); 43 | res.json({ 44 | total: total 45 | }); 46 | }); 47 | }; 48 | 49 | exports.remove = function(req, res) { 50 | var remindId = req.body.remindId; 51 | Remind.getOne(remindId, function(err, remind) { 52 | if (err) return callback(err); 53 | remind.remove(function(err) { 54 | if (err) return res.json(500); 55 | res.json(null); 56 | }); 57 | }); 58 | }; 59 | 60 | exports.setReaded = function(req, res) { 61 | var remindIds = req.body.remindIds; 62 | async.each(remindIds, function(remindId, callback) { 63 | Remind.getOne(remindId, function(err, remind) { 64 | if (err) return callback(err); 65 | remind.readed = true; 66 | remind.update(function(err) { 67 | if (err) return callback(err); 68 | callback(null); 69 | }); 70 | }); 71 | }, function(err) { 72 | if (err) return res.json(500); 73 | res.json(null); 74 | }); 75 | }; -------------------------------------------------------------------------------- /routes/Actions/TagAction.js: -------------------------------------------------------------------------------- 1 | var Tag = require("../Model/Tag.js"), 2 | User = require("../Model/User.js"), 3 | Article = require("../Model/Article.js"), 4 | async = require("async"); 5 | 6 | exports.create = function(req, res) { 7 | var tag = new Tag({ 8 | name: req.body.name, 9 | color: req.body.color 10 | }); 11 | tag.save(function(err, newTag) { 12 | if (err) return res.json(500, { 13 | message: err.message 14 | }); 15 | res.json({ 16 | tag: newTag 17 | }); 18 | }); 19 | }; 20 | 21 | exports.listAll = function(req, res) { 22 | var name = req.query.name; 23 | Tag.getAll(function(err, tags) { 24 | if (err) return res.json(500, { 25 | message: err.message 26 | }); 27 | res.json({ 28 | tags: tags 29 | }); 30 | }); 31 | }; 32 | 33 | exports.listUserTags = function(req, res) { 34 | User.get(req.body.username, function(err, user) { 35 | if (err) return req.json(500, { 36 | message: err.message 37 | }); 38 | async.map(user.tags, function(tagId, callback) { 39 | Tag.get(tagId, function(err, tag) { 40 | if (err) return callback(tag); 41 | callback(null, tag); 42 | }); 43 | }, function(err, tags) { 44 | if (err) return req.json(500, { 45 | message: err.message 46 | }); 47 | res.json({ 48 | tags: tags 49 | }); 50 | }); 51 | }); 52 | }; 53 | 54 | exports.listArticleTags = function(req, res) { 55 | Article.get(req.body.articleId, function(err, article) { 56 | if (err) return req.json(500, { 57 | message: err.message 58 | }); 59 | async.map(article.tags, function(tagId, callback) { 60 | Tag.get(tagId, function(err, tag) { 61 | if (err) return callback(tag); 62 | callback(null, tag); 63 | }); 64 | }, function(err, tags) { 65 | if (err) return req.json(500, { 66 | message: err.message 67 | }); 68 | res.json({ 69 | tags: tags 70 | }); 71 | }); 72 | }); 73 | }; 74 | 75 | exports.listFuzzy = function(req, res) { 76 | var name = req.query.name; 77 | Tag.getByFuzzyName(name, function(err, tags) { 78 | if (err) return res.json(500, { 79 | message: err.message 80 | }); 81 | res.json({ 82 | tags: tags 83 | }); 84 | }); 85 | }; -------------------------------------------------------------------------------- /public/javascripts/page/listArticle.js: -------------------------------------------------------------------------------- 1 | (function($, window) { 2 | var curPage = 0, 3 | MAX_PANELS = $(window).width() >= 992?3:2, 4 | __draw = function(article) { 5 | var $row = $(".g-row:last"), 6 | $arts = $(".g-arts"), 7 | $art; 8 | if ($(".g-art", $row).length === MAX_PANELS || $row.length === 0) { 9 | $row = $("
").addClass("g-row"); 10 | $arts.append($row); 11 | } 12 | 13 | $art = $("
").addClass("u-panel g-art"); 14 | $art.append("
" + article.writeTime + "
") 15 | .append("
") 16 | .append("
" + article.title + "
") 17 | .click(function(event) { 18 | window.location.href = "/article_load?articleId=" + article.id; 19 | }); 20 | $(document).trigger("user.getInfo", [article.writer, 21 | function(err, user) { 22 | if (err) return; 23 | $(".u-avatar", $art).append(""); 24 | } 25 | ]); 26 | $row.append($art); 27 | }, 28 | __readMore = function(event) { 29 | $(document).unbind("scroll", __readMore); 30 | $(".u-more").text("正在加载..."); 31 | var perPage = 10; 32 | if ($(window).height() + $(window).scrollTop() >= $('body').height()) { 33 | $(document).trigger("article.getAll", [curPage, perPage, 34 | function(err, articles) { 35 | if (err) { 36 | return; 37 | } 38 | var i, m; 39 | for (i = 0, m = articles.length; i < m; i++) { 40 | __draw(articles[i]); 41 | } 42 | curPage++; 43 | if (articles.length < perPage) { 44 | $(".u-more").text("已经没有更多文章了"); 45 | } else { 46 | $(document).bind("scroll", __readMore); 47 | $(".u-more").text("向下滚动加载更多"); 48 | $(document).scroll(); 49 | } 50 | } 51 | ]); 52 | } 53 | }; 54 | $("pre code").each(function(i, e) { 55 | hljs.highlightBlock(e); 56 | }); 57 | $("#articles tbody tr").click(function(event) { 58 | var that = $(this); 59 | window.location.href = "article_load?articleId=" + that.attr("aid"); 60 | }); 61 | $(document).scroll(__readMore); 62 | $(document).scroll(); 63 | }(jQuery, window)); -------------------------------------------------------------------------------- /routes/Model/User.js: -------------------------------------------------------------------------------- 1 | var commonDao = require("./CommonDAO.js"), 2 | collectionName = "user", 3 | __resultToListFn = function(callback) { 4 | return function(err, results) { 5 | var i; 6 | if (err) { 7 | return callback(err); 8 | } 9 | for (i = results.length; i--;) { 10 | results[i] = new User(results[i]); 11 | } 12 | callback(err, results); 13 | 14 | }; 15 | }; 16 | 17 | function User(user) { 18 | this.username = user.username; 19 | this.nickname = user.nickname; 20 | this.password = user.password; 21 | this.owner = user.owner; 22 | this.avatar = user.avatar; 23 | this.tags = user.tags; 24 | } 25 | 26 | module.exports = User; 27 | 28 | User.prototype.save = function(callback) { 29 | commonDao.save(collectionName, { 30 | username: this.username, 31 | password: this.password, 32 | nickname: this.nickname, 33 | owner: this.owner, 34 | avatar: this.avatar, 35 | tags: this.tags 36 | }, function(err, result) { 37 | if (err) return callback(err); 38 | if (!result[0]) return new Error("保存评论失败"); 39 | return callback(err, new User(result[0])); 40 | }); 41 | }; 42 | 43 | User.prototype.remove = function(callback) { 44 | commonDao.remove(collectionName, { 45 | username: this.username 46 | }, callback); 47 | }; 48 | 49 | User.prototype.update = function(callback) { 50 | commonDao.update(collectionName, { 51 | username: this.username 52 | }, { 53 | username: this.username, 54 | password: this.password, 55 | nickname: this.nickname, 56 | owner: this.owner, 57 | avatar: this.avatar, 58 | tags: this.tags 59 | }, callback); 60 | }; 61 | 62 | User.get = function(username, callback) { 63 | commonDao.findOne(collectionName, { 64 | username: username 65 | }, function(err, result) { 66 | if (err) return callback(err); 67 | callback(err, result ? new User(result) : result); 68 | }); 69 | }; 70 | 71 | User.getAll = function(curPage, perPage, callback) { 72 | commonDao.find(collectionName, { 73 | page: { 74 | curPage: curPage, 75 | perPage: perPage 76 | }, 77 | sort: { 78 | username: 1 79 | } 80 | }, __resultToListFn(callback)); 81 | }; 82 | 83 | User.countAll = function(callback) { 84 | commonDao.count(collectionName, {}, callback); 85 | }; -------------------------------------------------------------------------------- /public/javascripts/exp/broadchat.js: -------------------------------------------------------------------------------- 1 | (function($, window) { 2 | var ws, 3 | $chat = $("#chat"), 4 | $users = $("#users"), 5 | $logs = $("#logs"), 6 | curUser = $chat.attr("nick"); 7 | if (window.WebSocket === undefined) { 8 | $chat.text("您的浏览器不支持WebSocket!"); 9 | } 10 | if (window.ws) { 11 | ws.close(); 12 | } 13 | ws = new WebSocket("ws://localhost:3001"); 14 | ws.onopen = function(event) { 15 | $("#nickname").val("游客"); 16 | $logs.append("
您已加入版聊
"); 17 | }; 18 | ws.onclose = function(event) { 19 | $logs.append("
您已断开版聊
"); 20 | }; 21 | ws.onmessage = function(event) { 22 | var data = JSON.parse(event.data); 23 | if (data.type === "message") { 24 | $chat.append("

" + data.nick + "(" + data.time + "): " + data.message + "

"); 25 | } else if (data.type === "join") { 26 | if ($("p[uid='" + data.uid + "']", $users).length === 0) { 27 | $users.append("

" + data.nick + "

"); 28 | $logs.append("
" + data.nick + "加入了版聊
"); 29 | } 30 | } else if (data.type === "exit") { 31 | $("p[uid='" + data.uid + "']", $users).remove(); 32 | $logs.append("
" + data.nick + "离开了版聊
"); 33 | } else if (data.type === "nickname") { 34 | $("#nickname").val(data.nick); 35 | $("p[uid='" + data.uid + "']", $users).text(data.nick); 36 | $logs.append("
" + data.oldnick + " 修改昵称为 " + data.nick + "
"); 37 | } 38 | }; 39 | $("#send").click(function(event) { 40 | var message = $("#message").val(); 41 | if (message.trim() !== "") { 42 | ws.send(JSON.stringify({ 43 | time: new Date().getTime(), 44 | message: message, 45 | type: "message" 46 | })); 47 | $("#message").val(""); 48 | } 49 | }); 50 | $("#changeNick").click(function(event) { 51 | var nick = $("#nickname").val(); 52 | if (nick.trim() !== "") { 53 | ws.send(JSON.stringify({ 54 | nick: nick, 55 | type: "nickname" 56 | })); 57 | } 58 | }); 59 | $("#message").bind("keypress", function(event) { 60 | if ((event.keyCode || event.which) == 13) { 61 | $("#send").click(); 62 | } 63 | }).focus(); 64 | }(jQuery, window)); -------------------------------------------------------------------------------- /public/stylesheets/pageTools/tag.css: -------------------------------------------------------------------------------- 1 | .b-label-1 { 2 | background-color: #428bca 3 | } 4 | 5 | .b-label-2 { 6 | background-color: #5cb85c 7 | } 8 | 9 | .b-label-3 { 10 | background-color: #5cb0de 11 | } 12 | 13 | .b-label-4 { 14 | background-color: #f0ad4e 15 | } 16 | 17 | .b-label-5 { 18 | background-color: #d9543f 19 | } 20 | 21 | .u-label-picker { 22 | cursor: pointer; 23 | margin: 5px; 24 | font-size: 14px; 25 | display: inline-block; 26 | } 27 | 28 | .u-label { 29 | font-size: 15px; 30 | height: 23px; 31 | cursor: pointer; 32 | } 33 | 34 | .a-label{ 35 | margin: 5px; 36 | display: inline-block; 37 | } 38 | 39 | .a-label:hover{ 40 | -webkit-animation: a_label 1s linear infinite; 41 | -moz-animation: a_label 1s linear infinite; 42 | animation: a_label 1s linear infinite; 43 | } 44 | 45 | @-webkit-keyframes a_label{ 46 | 0% 100% { 47 | -webkit-transform: scale(1); 48 | } 49 | 50% { 50 | -webkit-transform: scale(1.2); 51 | } 52 | } 53 | 54 | @-moz-keyframes a_label{ 55 | 0% 100% { 56 | -moz-transform: scale(1); 57 | } 58 | 50% { 59 | -moz-transform: scale(1.2); 60 | } 61 | } 62 | 63 | @keyframes a_label{ 64 | 0% 100% { 65 | transform: scale(1); 66 | } 67 | 50% { 68 | transform: scale(1.2); 69 | } 70 | } 71 | 72 | .a-label-strike { 73 | -webkit-animation: labelstrike 1s linear 3; 74 | -moz-animation: labelstrike 1s linear 3; 75 | animation: labelstrike 1s linear 3; 76 | } 77 | 78 | @-webkit-keyframes labelstrike{ 79 | 0% { 80 | -webkit-transform : scale(1); 81 | } 82 | 50%{ 83 | -webkit-transform : scale(1.25); 84 | } 85 | 100% { 86 | -webkit-transform : scale(1); 87 | } 88 | } 89 | 90 | @-moz-keyframes labelstrike{ 91 | 0% { 92 | -moz-transform : scale(1); 93 | } 94 | 50%{ 95 | -moz-transform : scale(1.25); 96 | } 97 | 100% { 98 | -moz-transform : scale(1); 99 | } 100 | } 101 | 102 | @keyframes labelstrike{ 103 | 0% { 104 | transform : scale(1); 105 | } 106 | 50%{ 107 | transform : scale(1.25); 108 | } 109 | 100% { 110 | transform : scale(1); 111 | } 112 | } 113 | 114 | .b-label-loading { 115 | background: url("/images/loading.gif") no-repeat; 116 | background-position: center; 117 | min-height: 32px; 118 | min-width: 32px; 119 | } -------------------------------------------------------------------------------- /public/stylesheets/page/tag.css: -------------------------------------------------------------------------------- 1 | .b-label-1 { 2 | background-color: #428bca 3 | } 4 | 5 | .b-label-2 { 6 | background-color: #5cb85c 7 | } 8 | 9 | .b-label-3 { 10 | background-color: #5cb0de 11 | } 12 | 13 | .b-label-4 { 14 | background-color: #f0ad4e 15 | } 16 | 17 | .b-label-5 { 18 | background-color: #d9543f 19 | } 20 | 21 | .u-label-picker { 22 | cursor: pointer; 23 | margin: 5px; 24 | font-size: 14px 25 | } 26 | 27 | .u-label { 28 | font-size: 15px; 29 | cursor: pointer; 30 | } 31 | 32 | .a-label-rotateX{ 33 | margin: 5px; 34 | display: inline-block; 35 | } 36 | 37 | .a-label-rotateX:hover{ 38 | -webkit-animation: labelrotatex 1s linear infinite; 39 | -moz-animation: labelrotatex 1s linear infinite; 40 | animation: labelrotatex 1s linear infinite; 41 | } 42 | 43 | @-webkit-keyframes labelrotatex{ 44 | from { 45 | -webkit-transform : rotateX(0deg); 46 | } 47 | to { 48 | -webkit-transform : rotateX(360deg); 49 | } 50 | } 51 | 52 | @-moz-keyframes labelrotatex{ 53 | from { 54 | -moz-transform : rotateX(0deg); 55 | } 56 | to { 57 | -moz-transform : rotateX(360deg); 58 | } 59 | } 60 | 61 | @keyframes labelrotatex{ 62 | from { 63 | transform : rotateX(0deg); 64 | } 65 | to { 66 | transform : rotateX(360deg); 67 | } 68 | } 69 | 70 | .a-label-strike { 71 | -webkit-animation: labelstrike 1s linear 3; 72 | -moz-animation: labelstrike 1s linear 3; 73 | animation: labelstrike 1s linear 3; 74 | } 75 | 76 | @-webkit-keyframes labelstrike{ 77 | 0% { 78 | -webkit-transform : scale(1); 79 | } 80 | 50%{ 81 | -webkit-transform : scale(1.25); 82 | } 83 | 100% { 84 | -webkit-transform : scale(1); 85 | } 86 | } 87 | 88 | @-moz-keyframes labelstrike{ 89 | 0% { 90 | -moz-transform : scale(1); 91 | } 92 | 50%{ 93 | -moz-transform : scale(1.25); 94 | } 95 | 100% { 96 | -moz-transform : scale(1); 97 | } 98 | } 99 | 100 | @keyframes labelstrike{ 101 | 0% { 102 | transform : scale(1); 103 | } 104 | 50%{ 105 | transform : scale(1.25); 106 | } 107 | 100% { 108 | transform : scale(1); 109 | } 110 | } 111 | 112 | .b-label-loading { 113 | background: url("/images/loading.gif") no-repeat; 114 | background-position: center; 115 | min-height: 32px; 116 | min-width: 32px; 117 | } -------------------------------------------------------------------------------- /public/stylesheets/github.css: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | github.com style (c) Vasily Polovnyov 4 | 5 | */ 6 | 7 | pre code { 8 | display: block; padding: 0.5em; 9 | color: #333; 10 | background: #f8f8ff 11 | } 12 | 13 | pre .comment, 14 | pre .template_comment, 15 | pre .diff .header, 16 | pre .javadoc { 17 | color: #998; 18 | font-style: italic 19 | } 20 | 21 | pre .keyword, 22 | pre .css .rule .keyword, 23 | pre .winutils, 24 | pre .javascript .title, 25 | pre .nginx .title, 26 | pre .subst, 27 | pre .request, 28 | pre .status { 29 | color: #333; 30 | font-weight: bold 31 | } 32 | 33 | pre .number, 34 | pre .hexcolor, 35 | pre .ruby .constant { 36 | color: #099; 37 | } 38 | 39 | pre .string, 40 | pre .tag .value, 41 | pre .phpdoc, 42 | pre .tex .formula { 43 | color: #d14 44 | } 45 | 46 | pre .title, 47 | pre .id, 48 | pre .coffeescript .params, 49 | pre .scss .preprocessor { 50 | color: #900; 51 | font-weight: bold 52 | } 53 | 54 | pre .javascript .title, 55 | pre .lisp .title, 56 | pre .clojure .title, 57 | pre .subst { 58 | font-weight: normal 59 | } 60 | 61 | pre .class .title, 62 | pre .haskell .type, 63 | pre .vhdl .literal, 64 | pre .tex .command { 65 | color: #458; 66 | font-weight: bold 67 | } 68 | 69 | pre .tag, 70 | pre .tag .title, 71 | pre .rules .property, 72 | pre .django .tag .keyword { 73 | color: #000080; 74 | font-weight: normal 75 | } 76 | 77 | pre .attribute, 78 | pre .variable, 79 | pre .lisp .body { 80 | color: #008080 81 | } 82 | 83 | pre .regexp { 84 | color: #009926 85 | } 86 | 87 | pre .class { 88 | color: #458; 89 | font-weight: bold 90 | } 91 | 92 | pre .symbol, 93 | pre .ruby .symbol .string, 94 | pre .lisp .keyword, 95 | pre .tex .special, 96 | pre .prompt { 97 | color: #990073 98 | } 99 | 100 | pre .built_in, 101 | pre .lisp .title, 102 | pre .clojure .built_in { 103 | color: #0086b3 104 | } 105 | 106 | pre .preprocessor, 107 | pre .pi, 108 | pre .doctype, 109 | pre .shebang, 110 | pre .cdata { 111 | color: #999; 112 | font-weight: bold 113 | } 114 | 115 | pre .deletion { 116 | background: #fdd 117 | } 118 | 119 | pre .addition { 120 | background: #dfd 121 | } 122 | 123 | pre .diff .change { 124 | background: #0086b3 125 | } 126 | 127 | pre .chunk { 128 | color: #aaa 129 | } 130 | -------------------------------------------------------------------------------- /routes/index.js: -------------------------------------------------------------------------------- 1 | var Article = require("./Model/Article.js"), 2 | User = require("./Model/User.js"), 3 | Comment = require("./Model/Comment.js"), 4 | Admire = require("./Model/Admire.js"), 5 | Bookmark = require("./Model/Bookmark.js"), 6 | markdown = require("markdown").markdown, 7 | async = require("async"), 8 | moment = require("moment"); 9 | 10 | exports.index = function(req, res) { 11 | var page = (req.query.page || 1) - 1, 12 | artPerPage = req.query.artPerPage || 5; 13 | async.waterfall([ 14 | 15 | function(callback) { 16 | Article.getAll(page, artPerPage, function(err, articles) { 17 | if (err) return callback(err); 18 | callback(err, articles); 19 | }); 20 | }, 21 | function(articles, callback) { 22 | Article.countAll(function(err, total) { 23 | if (err) return callback(err); 24 | callback(err, articles, total); 25 | }); 26 | } 27 | ], function(err, articles, total) { 28 | var i, 29 | totalPage, 30 | curPage, 31 | startPage, 32 | endPage; 33 | if (err) return res.render("error", { 34 | message: "获取文章列表时发生错误..." 35 | }); 36 | for (i = articles.length; i--;) { 37 | articles[i].content = markdown.toHTML(articles[i].content); 38 | articles[i].writeTime = moment(articles[i].writeTime).fromNow(); 39 | } 40 | totalPage = Math.ceil(total / artPerPage); 41 | curPage = page + 1; 42 | 43 | if (curPage <= 2) { 44 | startPage = 1; 45 | endPage = totalPage >= 5 ? 5 : totalPage; 46 | } else if (curPage >= totalPage - 2) { 47 | endPage = totalPage; 48 | startPage = totalPage >= 5 ? totalPage - 4 : 1; 49 | } else { 50 | startPage = curPage - 2; 51 | endPage = curPage + 2; 52 | } 53 | res.render("index", { 54 | articles: articles, 55 | curPage: page + 1, 56 | artPerPage: artPerPage, 57 | totalPage: totalPage, 58 | startPage: startPage, 59 | endPage: endPage 60 | }); 61 | }); 62 | }; 63 | 64 | exports.userCenter = function(req, res) { 65 | var username = req.query.username || req.session.user.username; 66 | User.get(username, function(err, user) { 67 | if (err) return res.render("error", { 68 | message: err.message 69 | }); 70 | res.render("userCenter", { 71 | curUser: user 72 | }); 73 | }); 74 | }; 75 | 76 | exports.advicePage = function(req, res) { 77 | res.render("advice"); 78 | }; 79 | 80 | exports.searchPage = function(req, res) { 81 | res.render("search"); 82 | }; 83 | 84 | exports.articleListPage = function(req, res){ 85 | res.render("listArticle"); 86 | }; -------------------------------------------------------------------------------- /public/stylesheets/monokai.css: -------------------------------------------------------------------------------- 1 | /* 2 | Monokai style - ported by Luigi Maselli - http://grigio.org 3 | */ 4 | 5 | pre code { 6 | display: block; padding: 0.5em; 7 | background: #272822; 8 | } 9 | 10 | pre .tag, 11 | pre .tag .title, 12 | pre .keyword, 13 | pre .literal, 14 | pre .strong, 15 | pre .change, 16 | pre .winutils, 17 | pre .flow, 18 | pre .lisp .title, 19 | pre .clojure .built_in, 20 | pre .nginx .title, 21 | pre .tex .special { 22 | color: #F92672; 23 | } 24 | 25 | pre code { 26 | color: #DDD; 27 | } 28 | 29 | pre code .constant { 30 | color: #66D9EF; 31 | } 32 | 33 | pre .code, 34 | pre .class .title, 35 | pre .header { 36 | color: white; 37 | } 38 | 39 | pre .link_label, 40 | pre .attribute, 41 | pre .symbol, 42 | pre .symbol .string, 43 | pre .value, 44 | pre .regexp { 45 | color: #BF79DB; 46 | } 47 | 48 | pre .link_url, 49 | pre .tag .value, 50 | pre .string, 51 | pre .bullet, 52 | pre .subst, 53 | pre .title, 54 | pre .emphasis, 55 | pre .haskell .type, 56 | pre .preprocessor, 57 | pre .ruby .class .parent, 58 | pre .built_in, 59 | pre .sql .aggregate, 60 | pre .django .template_tag, 61 | pre .django .variable, 62 | pre .smalltalk .class, 63 | pre .javadoc, 64 | pre .django .filter .argument, 65 | pre .smalltalk .localvars, 66 | pre .smalltalk .array, 67 | pre .attr_selector, 68 | pre .pseudo, 69 | pre .addition, 70 | pre .stream, 71 | pre .envvar, 72 | pre .apache .tag, 73 | pre .apache .cbracket, 74 | pre .tex .command, 75 | pre .prompt { 76 | color: #A6E22E; 77 | } 78 | 79 | pre .comment, 80 | pre .java .annotation, 81 | pre .blockquote, 82 | pre .horizontal_rule, 83 | pre .python .decorator, 84 | pre .template_comment, 85 | pre .pi, 86 | pre .doctype, 87 | pre .deletion, 88 | pre .shebang, 89 | pre .apache .sqbracket, 90 | pre .tex .formula { 91 | color: #75715E; 92 | } 93 | 94 | pre .keyword, 95 | pre .literal, 96 | pre .css .id, 97 | pre .phpdoc, 98 | pre .title, 99 | pre .header, 100 | pre .haskell .type, 101 | pre .vbscript .built_in, 102 | pre .sql .aggregate, 103 | pre .rsl .built_in, 104 | pre .smalltalk .class, 105 | pre .diff .header, 106 | pre .chunk, 107 | pre .winutils, 108 | pre .bash .variable, 109 | pre .apache .tag, 110 | pre .tex .special, 111 | pre .request, 112 | pre .status { 113 | font-weight: bold; 114 | } 115 | 116 | pre .coffeescript .javascript, 117 | pre .javascript .xml, 118 | pre .tex .formula, 119 | pre .xml .javascript, 120 | pre .xml .vbscript, 121 | pre .xml .css, 122 | pre .xml .cdata { 123 | opacity: 0.5; 124 | } 125 | -------------------------------------------------------------------------------- /views/editArticle.jade: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | block style 4 | link(rel='stylesheet', href='/stylesheets/github.css') 5 | link(rel='stylesheet', href='/stylesheets/pageTools/tag.css') 6 | link(rel='stylesheet', href='/stylesheets/page/writeArticle.css') 7 | block content 8 | div(class="container") 9 | h2 10 | strong 修改文章 11 | div(class="u-panel") 12 | div(class="alert alert-danger", id="formValidate", style="display:none") 13 | div(class="form-group") 14 | label(for="inputTitle") 15 | h3 标题: 16 | input(class="form-control", type="text", id="inputTitle", placeholder="文章标题", value="#{article.title}") 17 | div(class="panel", style="padding:10px;height:80%;background:transparent") 18 | div(class="u-panel-body" id="leftPanel") 19 | textarea(class="u-pane", id="inputArticle", style="resize: none;") 20 | =article.content 21 | div(class="u-panel-body" id="rightPanel") 22 | div(class="u-pane", id="outputArticle") 23 | div(style="clear:both") 24 | div(class="row") 25 | input(type="hidden", name="tags") 26 | div(class="col-md-6") 27 | h2 本文标签 28 | hr 29 | div(id="articleTags", aid="#{article.id}") 30 | div(class="col-md-6") 31 | h2 所有标签 32 | hr 33 | div(id="allTags") 34 | hr 35 | span(class="label b-label-1", style="padding:8px 5px", id="newTag") 36 | input(id="newTagInput",type="text", placeholder="标签名", color="#428bca") 37 | span(class="glyphicon glyphicon-plus u-tag-create", style="margin-left:5px;cursor:pointer", id="createTag") 38 | span(class="label b-label-1 u-label-picker", value=1, color="#428bca") 深蓝 39 | span(class="label b-label-2 u-label-picker", value=2, color="#5cb85c") 绿色 40 | span(class="label b-label-3 u-label-picker", value=3, color="#5cb0de") 淡蓝 41 | span(class="label b-label-4 u-label-picker", value=4, color="#f0ad4e") 黄色 42 | span(class="label b-label-5 u-label-picker", value=5, color="#d9543f") 红色 43 | div(class="form-group", style="margin-top:20px") 44 | button(class="btn btn-primary", style="width:100%", id="saveArtBtn", aid="#{article.id}") 保存文章 45 | 46 | block javascript 47 | script(type='text/javascript', src='/javascripts/tools/showdown.js') 48 | script(type='text/javascript', src='/javascripts/tools/highlight.pack.js') 49 | script(type='text/javascript', src='/javascripts/pageTools/Tag.js') 50 | script(type='text/javascript', src='/javascripts/page/editArticle.js') 51 | -------------------------------------------------------------------------------- /routes/Model/Admire.js: -------------------------------------------------------------------------------- 1 | var commonDao = require("./CommonDAO.js"), 2 | collectionName = "admire", 3 | uuid = require("node-uuid"), 4 | __resultToListFn = function(callback) { 5 | return function(err, results) { 6 | var i; 7 | if (err) return callback(err); 8 | for (i = results.length; i--;) { 9 | results[i] = new Admire(results[i]); 10 | } 11 | callback(err, results); 12 | 13 | }; 14 | }; 15 | 16 | function Admire(admire) { 17 | this.username = admire.username; 18 | this.commentId = admire.commentId; 19 | this.time = admire.time; 20 | this.id = admire.id; 21 | } 22 | 23 | module.exports = Admire; 24 | 25 | Admire.prototype.save = function(callback) { 26 | commonDao.save(collectionName, { 27 | username: this.username, 28 | commentId: this.commentId, 29 | time: new Date().getTime(), 30 | id: uuid.v4() 31 | }, function(err, result) { 32 | if (err) return callback(err); 33 | if (!result[0]) return callback(new Error("保存失败")); 34 | callback(err, new Admire(result[0])); 35 | }); 36 | }; 37 | 38 | Admire.get = function(admireId, callback) { 39 | commonDao.findOne(collectionName, { 40 | id: admireId 41 | }, function(err, result) { 42 | if (err) return callback(err); 43 | callback(err, result ? new Admire(result) : result); 44 | }); 45 | }; 46 | 47 | Admire.removeByComment = function(commentId, callback) { 48 | commonDao.remove(collectionName, { 49 | commentId: commentId 50 | }, callback); 51 | }; 52 | 53 | Admire.getByUser = function(username, curPage, perPage, callback) { 54 | commonDao.find(collectionName, { 55 | condition: { 56 | username: username 57 | }, 58 | sort: { 59 | time: -1 60 | }, 61 | page: { 62 | curPage: curPage, 63 | perPage: perPage 64 | } 65 | }, __resultToListFn(callback)); 66 | }; 67 | 68 | Admire.countByUser = function(username, callback) { 69 | commonDao.count(collectionName, { 70 | username: username 71 | }, callback); 72 | }; 73 | 74 | Admire.prototype.remove = function(callback) { 75 | commonDao.remove(collectionName, { 76 | username: this.username, 77 | commentId: this.commentId 78 | }, callback); 79 | }; 80 | 81 | Admire.countByComment = function(commentId, callback) { 82 | commonDao.count(collectionName, { 83 | commentId: commentId 84 | }, callback); 85 | }; 86 | 87 | Admire.checkAdmired = function(username, commentId, callback) { 88 | commonDao.findOne(collectionName, { 89 | username: username, 90 | commentId: commentId 91 | }, function(err, result) { 92 | if (err) return callback(err); 93 | if (!result) return callback(err, false); 94 | return callback(err, true); 95 | }); 96 | }; -------------------------------------------------------------------------------- /public/stylesheets/page/articleDetail.css: -------------------------------------------------------------------------------- 1 | @media (max-width: 992px) { 2 | div.g-art .g-art-body { 3 | width: 100%; 4 | } 5 | } 6 | 7 | .u-flag { 8 | position: relative; 9 | left : -50px; 10 | height: 60px; 11 | margin-bottom: 30px; 12 | } 13 | 14 | .u-flag .u-flag-left { 15 | min-width: 300px; 16 | max-width: 1000px; 17 | height: 60px; 18 | position: absolute; 19 | padding: 0 20px; 20 | background-color: #d9534f; 21 | text-align: center; 22 | color: white; 23 | } 24 | 25 | .u-flag .u-flag-left h3{ 26 | text-overflow: ellipsis; 27 | white-space : nowrap; 28 | overflow: hidden; 29 | } 30 | 31 | .u-flag .u-flag-left:before { 32 | position: absolute; 33 | display: block; 34 | content : ""; 35 | right: -30px; 36 | border-bottom : 30px solid #d9534f; 37 | border-top : 30px solid #d9534f; 38 | border-right : 30px solid transparent; 39 | } 40 | 41 | .u-flag .u-flag-left:after { 42 | position: absolute; 43 | display: block; 44 | content : ""; 45 | width : 60px; 46 | height: 30px; 47 | left : -15px; 48 | top: 21px; 49 | background-color: #ac2925; 50 | -webkit-transform : rotate(90deg) skew(-20deg); 51 | -moz-transform : rotate(90deg) skew(-20deg); 52 | transform : rotate(90deg) skew(-20deg); 53 | z-index: -1; 54 | } 55 | 56 | .g-art-footer .u-bookmark { 57 | float:right; 58 | } 59 | 60 | .u-more { 61 | width: 100%; 62 | background-color: #f0f0f0; 63 | text-align: center; 64 | padding: 10px; 65 | border-bottom-right-radius: 20px; 66 | border-bottom-left-radius: 20px; 67 | cursor: pointer; 68 | } 69 | 70 | .u-more:hover { 71 | background-color: rgb(66, 139, 202); 72 | color: white; 73 | } 74 | 75 | .g-reply-form { 76 | display: none; 77 | margin-left: 100px; 78 | margin-top:30px; 79 | } 80 | 81 | .g-art { 82 | width: 100%; 83 | margin-top: 40px; 84 | } 85 | 86 | .g-art:after { 87 | display: table; 88 | clear: both; 89 | content: ""; 90 | } 91 | 92 | .g-art .g-art-body { 93 | float: left; 94 | width: 73%; 95 | } 96 | 97 | .g-art .g-art-info { 98 | float: right; 99 | width: 25%; 100 | } 101 | 102 | .g-art .g-art-info .g-user .u-avatar{ 103 | float: right; 104 | width: 33%; 105 | } 106 | 107 | .g-art .g-art-info .g-user .u-nick{ 108 | float: left; 109 | width: 66%; 110 | margin-top: 20px; 111 | 112 | text-align: center; 113 | font-weight: bolder; 114 | font-size: 150%; 115 | } 116 | 117 | .g-art .g-art-info .u-time { 118 | margin-top: 20px; 119 | text-align: center; 120 | font-weight: bolder; 121 | font-size: 120%; 122 | } 123 | 124 | .g-art .g-art-info .u-tags { 125 | margin-top: 20px; 126 | } 127 | 128 | -------------------------------------------------------------------------------- /public/stylesheets/page/listArticle.css: -------------------------------------------------------------------------------- 1 | @media (max-width: 992px){ 2 | div.g-row .g-art { 3 | width: 35%; 4 | margin-left: 10%; 5 | } 6 | div.g-row:nth-child(odd) .g-art:nth-child(1) { 7 | margin-left: 0; 8 | } 9 | 10 | div.g-row:nth-child(even) .g-art:nth-child(1) { 11 | margin-left: 10%; 12 | } 13 | } 14 | 15 | .g-arts { 16 | width: 100%; 17 | float: left; 18 | margin-top: 40px; 19 | } 20 | 21 | .g-arts .g-row { 22 | width: 100%; 23 | padding: 40px 0 0 0; 24 | } 25 | 26 | .g-arts .g-row:after { 27 | display: table; 28 | content: ""; 29 | clear: both; 30 | } 31 | 32 | .g-row .g-art { 33 | position: relative; 34 | float: left; 35 | width: 22%; 36 | 37 | margin-left: 11%; 38 | 39 | cursor: pointer; 40 | } 41 | 42 | .g-row:nth-child(odd) .g-art:nth-child(1) { 43 | margin-left: 0; 44 | } 45 | 46 | .g-row:nth-child(even) .g-art:nth-child(1) { 47 | margin-left: 11%; 48 | } 49 | 50 | .g-row .g-art .u-time{ 51 | position: absolute; 52 | left: 10%; 53 | top: -10px; 54 | height: 20px; 55 | width: 60%; 56 | 57 | border-radius: 10px; 58 | box-shadow: 2px 2px 2px #8A8A8A; 59 | 60 | font-weight: bolder; 61 | background-color: rgba(255,255,255,0.8); 62 | text-align: center; 63 | } 64 | 65 | .g-row .g-art .u-title{ 66 | width: 100%; 67 | 68 | word-wrap: break-word; 69 | font-size: 150%; 70 | font-weight: bolder; 71 | text-align: center; 72 | } 73 | 74 | .g-row .g-art .u-avatar{ 75 | position: absolute; 76 | right: 10px; 77 | top: -15px; 78 | width: 30px; 79 | height: 30px; 80 | 81 | border-radius: 50%; 82 | 83 | box-shadow: 2px 2px 2px #8A8A8A; 84 | overflow: hidden; 85 | } 86 | 87 | .g-row .g-art .u-avatar img{ 88 | width: 100%; 89 | } 90 | 91 | .g-row .g-art:hover { 92 | -webkit-animation: art_rotate 1s linear infinite; 93 | -moz-animation: art_rotate 1s linear infinite; 94 | animation: art_rotate 1s linear infinite; 95 | } 96 | 97 | @-webkit-keyframes art_rotate { 98 | 0% 100% { 99 | -webkit-transform: scale(1); 100 | } 101 | 50% { 102 | -webkit-transform: scale(1.2); 103 | } 104 | } 105 | 106 | @-moz-keyframes art_rotate { 107 | 0% 100% { 108 | -moz-transform: scale(1); 109 | } 110 | 50% { 111 | -moz-transform: scale(1.2); 112 | } 113 | } 114 | 115 | @keyframes art_rotate { 116 | 0% 100% { 117 | transform: scale(1); 118 | } 119 | 50% { 120 | transform: scale(1.2); 121 | } 122 | } 123 | 124 | .u-more { 125 | margin-top:20px; 126 | float: right; 127 | font-weight: bolder; 128 | padding: 10px; 129 | } 130 | 131 | .u-more:hover { 132 | color: white; 133 | background-color: rgb(63, 93, 255); 134 | cursor: pointer; 135 | } -------------------------------------------------------------------------------- /public/stylesheets/page/userCenter.css: -------------------------------------------------------------------------------- 1 | .g-hide { 2 | display: none 3 | } 4 | 5 | .g-item { 6 | padding: 5px 10px 7 | } 8 | 9 | .g-item .u-avatar { 10 | float: left; 11 | } 12 | .g-item .g-info { 13 | margin-left : 100px; 14 | height: 30px 15 | } 16 | .g-item .g-info .u-time{ 17 | float:left; 18 | } 19 | 20 | .g-item .g-info .u-nick{ 21 | font-size: 120%; 22 | font-weight: bolder; 23 | } 24 | 25 | .g-item .g-info .u-opt{ 26 | float:right; 27 | padding : 5px 10px; 28 | border-radius: 10px; 29 | color: black; 30 | cursor: pointer; 31 | text-decoration: none 32 | } 33 | 34 | .g-item .g-info .u-opt:hover{ 35 | background-color: #f0f0f0 36 | } 37 | 38 | .g-item .u-title { 39 | margin-left : 60px; 40 | text-align: center 41 | } 42 | 43 | .g-item .u-title a{ 44 | text-decoration: none; 45 | font-size: 150% 46 | } 47 | 48 | .g-item .u-time { 49 | text-align: right 50 | } 51 | 52 | .g-item .u-content { 53 | margin-left : 100px; 54 | text-align: left; 55 | } 56 | 57 | .u-noitem { 58 | text-align: center 59 | } 60 | 61 | .a-fliker { 62 | display: inline-block; 63 | -webkit-animation: labelfliker 2s infinite; 64 | -moz-animation: labelfliker 2s infinite; 65 | animation: labelfliker 2s infinite; 66 | } 67 | 68 | .u-more { 69 | width: 100%; 70 | background-color: #428bca; 71 | color:#fff; 72 | text-align: center; 73 | padding: 10px; 74 | border-radius: 20px; 75 | cursor: pointer; 76 | } 77 | 78 | .u-more:hover { 79 | background-color: #8f8f8f; 80 | color: #f0f0f0; 81 | } 82 | 83 | @-moz-keyframes labelfliker { 84 | 0% { 85 | -moz-transform : scale(1); 86 | } 87 | 50% { 88 | -moz-transform : scale(1.15); 89 | } 90 | 100% { 91 | -moz-transform : scale(1); 92 | } 93 | } 94 | 95 | @-webkit-keyframes labelfliker { 96 | 0% { 97 | -webkit-transform : scale(1); 98 | } 99 | 50% { 100 | -webkit-transform : scale(1.15); 101 | } 102 | 100% { 103 | -webkit-transform : scale(1); 104 | } 105 | } 106 | 107 | @keyframes labelfliker { 108 | 0% { 109 | transform : scale(1); 110 | } 111 | 50% { 112 | transform : scale(1.15); 113 | } 114 | 100% { 115 | transform : scale(1); 116 | } 117 | } 118 | 119 | .g-row { 120 | width: 100%; 121 | } 122 | 123 | .g-row:after { 124 | display: table; 125 | content: ""; 126 | clear: both; 127 | } 128 | 129 | .g-row .g-left { 130 | float: left; 131 | width: 25%; 132 | } 133 | 134 | .g-row .g-right { 135 | float: right; 136 | width: 73%; 137 | } 138 | 139 | .list-group .list-group-item { 140 | background: transparent; 141 | } 142 | 143 | .list-group .list-group-item.active { 144 | background: rgb(66, 139, 202) 145 | } -------------------------------------------------------------------------------- /routes/Model/Comment.js: -------------------------------------------------------------------------------- 1 | var commonDao = require("./CommonDAO.js"), 2 | collectionName = "comment", 3 | uuid = require("node-uuid"), 4 | Admire = require("./Admire.js"), 5 | async = require("async"), 6 | __resultToListFn = function(callback) { 7 | return function(err, results) { 8 | var i; 9 | if (err) { 10 | return callback(err); 11 | } 12 | for (i = results.length; i--;) { 13 | results[i] = new Comment(results[i]); 14 | } 15 | callback(err, results); 16 | }; 17 | }; 18 | 19 | function Comment(comment) { 20 | this.comment = comment.comment; 21 | this.username = comment.username; 22 | this.reply = comment.reply; 23 | this.articleId = comment.articleId; 24 | this.time = comment.time; 25 | this.id = comment.id; 26 | } 27 | 28 | module.exports = Comment; 29 | 30 | Comment.prototype.save = function(callback) { 31 | commonDao.save(collectionName, { 32 | comment: this.comment, 33 | username: this.username, 34 | articleId: this.articleId, 35 | time: new Date().getTime(), 36 | reply: this.reply, 37 | id: uuid.v4() 38 | }, function(err, result) { 39 | if (err) return callback(err); 40 | if (!result[0]) return new Error("保存评论失败"); 41 | return callback(err, new Comment(result[0])); 42 | }); 43 | }; 44 | 45 | Comment.prototype.remove = function(callback) { 46 | var commentId = this.id; 47 | commonDao.remove(collectionName, { 48 | id: commentId 49 | }, callback); 50 | }; 51 | 52 | Comment.get = function(commentId, callback) { 53 | commonDao.findOne(collectionName, { 54 | id: commentId 55 | }, function(err, result) { 56 | if (err) return callback(err); 57 | callback(err, result ? new Comment(result) : result); 58 | }); 59 | }; 60 | 61 | Comment.getByArticle = function(articleId, curPage, perPage, callback) { 62 | commonDao.find(collectionName, { 63 | condition: { 64 | articleId: articleId 65 | }, 66 | sort: { 67 | time: -1 68 | }, 69 | page: { 70 | curPage: curPage, 71 | perPage: perPage 72 | } 73 | }, __resultToListFn(callback)); 74 | }; 75 | 76 | Comment.getByUser = function(username, curPage, perPage, callback) { 77 | commonDao.find(collectionName, { 78 | condition: { 79 | username: username 80 | }, 81 | sort: { 82 | time: -1 83 | }, 84 | page: { 85 | curPage: curPage, 86 | perPage: perPage 87 | } 88 | }, __resultToListFn(callback)); 89 | }; 90 | 91 | Comment.countByUser = function(username, callback) { 92 | commonDao.count(collectionName, { 93 | username: username 94 | }, callback); 95 | }; 96 | 97 | Comment.countByArticle = function(articleId, callback) { 98 | commonDao.count(collectionName, { 99 | articleId: articleId 100 | }, callback); 101 | }; -------------------------------------------------------------------------------- /views/writeArticle.jade: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | block style 4 | link(rel='stylesheet', href='/stylesheets/github.css') 5 | link(rel='stylesheet', href='/stylesheets/pageTools/tag.css') 6 | link(rel='stylesheet', href='/stylesheets/page/writeArticle.css') 7 | 8 | block content 9 | div(class="container") 10 | form(action="/nor/conf/article_save", method="post", id="articleForm") 11 | h2 12 | strong 写新文章 13 | div(class="u-panel") 14 | div(class="alert alert-danger", id="formValidate", style="display:none") 15 | 16 | div(class="form-group") 17 | label(for="inputTitle") 18 | h3 标题: 19 | input(class="form-control", type="text", name="title",id="inputTitle", placeholder="文章标题") 20 | div(class="panel panel-default", style="padding:10px;height:80%") 21 | div(class="u-panel-body" id="leftPanel") 22 | textarea(class="u-pane", id="inputArticle", name="content",style="resize: none;") 23 | div(class="u-panel-body" id="rightPanel") 24 | div(class="u-pane", id="outputArticle") 25 | div(style="clear:both") 26 | div(class="row") 27 | input(type="hidden", name="tags") 28 | div(class="col-sm-6") 29 | h2 本文标签 30 | hr 31 | div(id="articleTags") 32 | div(class="col-sm-6") 33 | h2 所有标签 34 | hr 35 | div(id="allTags") 36 | hr 37 | span(class="label b-label-1", style="padding:8px 5px", id="newTag") 38 | input(id="newTagInput",type="text", placeholder="标签名", color="#428bca") 39 | span(class="glyphicon glyphicon-plus u-tag-create", style="margin-left:5px;cursor:pointer", id="createTag") 40 | span(class="label b-label-1 u-label-picker", value=1, color="#428bca") 深蓝 41 | span(class="label b-label-2 u-label-picker", value=2, color="#5cb85c") 绿色 42 | span(class="label b-label-3 u-label-picker", value=3, color="#5cb0de") 淡蓝 43 | span(class="label b-label-4 u-label-picker", value=4, color="#f0ad4e") 黄色 44 | span(class="label b-label-5 u-label-picker", value=5, color="#d9543f") 红色 45 | div(class="form-group", style="margin-top:20px") 46 | button(class="btn btn-primary", style="width:100%", id="saveArtBtn", type="submit") 保存文章 47 | 48 | block javascript 49 | script(type='text/javascript', src='/javascripts/tools/showdown.js') 50 | script(type='text/javascript', src='/javascripts/tools/showdown_extensions/twitter.js') 51 | script(type='text/javascript', src='/javascripts/tools/highlight.pack.js') 52 | script(type='text/javascript', src='/javascripts/pageTools/Tag.js') 53 | script(type='text/javascript', src='/javascripts/page/writeArticle.js') -------------------------------------------------------------------------------- /public/javascripts/page/userDetail.js: -------------------------------------------------------------------------------- 1 | (function($, window) { 2 | var formValidate = $("#formValidate"), 3 | $allTags = $("#allTags"), 4 | $myTags = $("#myTags"), 5 | __addToMyTags = function(event) { 6 | var tag = event.data, 7 | $div = $("
" + tag.name + "
"); 8 | $existTag = $myTags.find("span[tid='" + tag.id + "']"); 9 | $div.click(__removeFromMyTags); 10 | if ($existTag.length === 0) { 11 | $myTags.append($div); 12 | } else { 13 | $existTag.parent().removeClass("a-label-strike"); 14 | setTimeout(function() { 15 | $existTag.parent().addClass("a-label-strike"); 16 | }, 0); 17 | } 18 | }, 19 | __removeFromMyTags = function(event) { 20 | $(this).remove(); 21 | }; 22 | $("#inputAvatar").change(function(event) { 23 | $("#avatarPreview").attr("src", $(this).val()); 24 | }); 25 | $("#detail").submit(function(event) { 26 | var that = $(this), 27 | mytags = $myTags.find("span"), 28 | tags = [], 29 | i, m; 30 | formValidate.hide(); 31 | if (!this.nickname.value) { 32 | formValidate.text("昵称不能为空").slideDown(); 33 | return false; 34 | } 35 | 36 | if (!this.nickname.value.match(/^[\w\-\u4e00-\u9fa5]{3,12}$/)) { 37 | formValidate.text("昵称必须为长度为3~12的中文、字母、数字、下划线").slideDown(); 38 | $(this.nickname).focus(); 39 | return false; 40 | } 41 | if (!this.password.value && this.password.value !== "") { 42 | if (!this.password.value.match(/^[a-zA-Z0-9]{5,15}$/)) { 43 | formValidate.text("密码必须为长度为5~15的字母或数字").slideDown(); 44 | $(this.password).focus(); 45 | return false; 46 | } 47 | if (this.password.value != this.passwordAgain.value) { 48 | formValidate.text("密码验证不正确,请重新填写").slideDown(); 49 | return false; 50 | } 51 | } 52 | for (i = mytags.length; i--;) { 53 | tags.push($(mytags[i]).attr("tid")); 54 | } 55 | this.tags.value = JSON.stringify(tags); 56 | }); 57 | 58 | $("#newTagInput").change(function() { 59 | $(this).removeClass("has-error"); 60 | }); 61 | 62 | $("#createTag").click(function(event) { 63 | var name = $("#newTagInput").val(), 64 | color = $("#newTagInput").attr("color"); 65 | if (!name) { 66 | $("#newTagInput").addClass("has-error"); 67 | return false; 68 | } 69 | $(document).trigger("tag.createTag", [name, color, $allTags,__addToMyTags,function(event) { 70 | $("#newTagInput").val(""); 71 | }]); 72 | }); 73 | 74 | $(document).trigger("tag.drawAllTags", [$allTags,__addToMyTags]); 75 | 76 | $(document).trigger("tag.drawUserTags", [$("#inputUsername").val(), $myTags, __removeFromMyTags]); 77 | 78 | $(".u-label-picker").click(function(event) { 79 | $("#newTag").attr("class", "label b-label-" + $(this).attr("value")).find("input").attr("color", $(this).attr("color")); 80 | }); 81 | }(jQuery, window)); -------------------------------------------------------------------------------- /routes/Model/Bookmark.js: -------------------------------------------------------------------------------- 1 | var commonDao = require("./CommonDAO.js"), 2 | collectionName = "bookmark", 3 | uuid = require("node-uuid"), 4 | __resultToListFn = function(callback) { 5 | return function(err, results) { 6 | var i; 7 | if (err) return callback(err); 8 | for (i = results.length; i--;) { 9 | results[i] = new Bookmark(results[i]); 10 | } 11 | callback(err, results); 12 | }; 13 | }; 14 | 15 | function Bookmark(bookmark) { 16 | this.username = bookmark.username; 17 | this.articleId = bookmark.articleId; 18 | this.time = bookmark.time; 19 | this.id = bookmark.id; 20 | } 21 | 22 | module.exports = Bookmark; 23 | 24 | Bookmark.prototype.save = function(callback) { 25 | commonDao.save(collectionName, { 26 | username: this.username, 27 | articleId: this.articleId, 28 | time: new Date().getTime(), 29 | id: uuid.v4() 30 | }, function(err, result) { 31 | if (err) return callback(err); 32 | if (!result[0]) return callback(new Error("保存失败")); 33 | callback(err, new Bookmark(result[0])); 34 | }); 35 | }; 36 | 37 | Bookmark.prototype.remove = function(callback) { 38 | commonDao.remove(collectionName, { 39 | username: this.username, 40 | articleId: this.articleId 41 | }, callback); 42 | }; 43 | 44 | 45 | Bookmark.get = function(bookmarkId, callback) { 46 | commonDao.findOne(collectionName, { 47 | id: bookmarkId 48 | }, function(err, result) { 49 | if (err) return callback(err); 50 | callback(err, result ? new Bookmark(result) : result); 51 | }); 52 | }; 53 | 54 | Bookmark.checkBooked = function(username, articleId, callback) { 55 | commonDao.findOne(collectionName, { 56 | username: username, 57 | articleId: articleId 58 | }, function(err, result) { 59 | if (err) return callback(err); 60 | if (!result) return callback(err, false); 61 | return callback(err, true); 62 | }); 63 | }; 64 | 65 | Bookmark.getByUser = function(username, curPage, perPage, callback) { 66 | commonDao.find(collectionName, { 67 | condition: { 68 | username: username 69 | }, 70 | sort: { 71 | time: -1 72 | }, 73 | page: { 74 | curPage: curPage, 75 | perPage: perPage 76 | } 77 | }, __resultToListFn(callback)); 78 | }; 79 | 80 | Bookmark.countByUser = function(username, callback) { 81 | commonDao.count(collectionName, { 82 | username: username 83 | }, callback); 84 | }; 85 | 86 | Bookmark.getByArticle = function(articleId, callback) { 87 | commonDao.find(collectionName, { 88 | condition: { 89 | articleId: articleId 90 | }, 91 | sort: { 92 | time: -1 93 | } 94 | }, __resultToListFn(callback)); 95 | }; 96 | 97 | Bookmark.countByArticle = function(articleId, callback) { 98 | commonDao.count(collectionName, { 99 | articleId: articleId 100 | }, function(err, total) { 101 | if (err) return callback(err); 102 | callback(err, total); 103 | }); 104 | }; -------------------------------------------------------------------------------- /public/javascripts/page/writeArticle.js: -------------------------------------------------------------------------------- 1 | (function($, window) { 2 | var formValidate = $("#formValidate"), 3 | converter = new Showdown.converter({ 4 | extensions: 'twitter' 5 | }), 6 | $articleTags = $("#articleTags"), 7 | $allTags = $("#allTags"), 8 | __addToArticleTags = function(event) { 9 | var tag = event.data, 10 | $div = $("
" + tag.name + "
"), 11 | $existTag = $articleTags.find("span[tid='" + tag.id + "']"); 12 | $div.click(__removeFromArticleTags); 13 | if ($existTag.length === 0) { 14 | $articleTags.append($div); 15 | } else { 16 | $existTag.parent().removeClass("a-label-strike"); 17 | setTimeout(function() { 18 | $existTag.parent().addClass("a-label-strike"); 19 | }, 0); 20 | } 21 | }, 22 | __removeFromArticleTags = function(event) { 23 | $(this).remove(); 24 | }; 25 | 26 | getWindowHeight = function() { 27 | if (window.innerHeight) 28 | return window.innerHeight; 29 | else if (document.documentElement && document.documentElement.clientHeight) 30 | return document.documentElement.clientHeight; 31 | else if (document.body) 32 | return document.body.clientHeight; 33 | }; 34 | 35 | $("#inputArticle").bind("keyup", function(event) { 36 | $("#outputArticle").html(converter.makeHtml(this.value)); 37 | $('pre code').each(function(i, e) { 38 | hljs.highlightBlock(e); 39 | }); 40 | }); 41 | 42 | $(window).bind("resize", function(event) { 43 | $("body").height(getWindowHeight() - 150); 44 | $(".panel").height(getWindowHeight() - 350); 45 | }).trigger("resize"); 46 | 47 | $(document).trigger("tag.drawAllTags", [$allTags, __addToArticleTags]); 48 | 49 | $("#createTag").click(function(event) { 50 | var name = $("#newTagInput").val(), 51 | color = $("#newTagInput").attr("color"); 52 | if (!name) { 53 | $("#newTagInput").addClass("has-error"); 54 | return false; 55 | } 56 | 57 | $(document).trigger("tag.createTag", [name, color, $allTags, __addToArticleTags, 58 | function(err, $tag) { 59 | if (err) { 60 | alert("创建标签失败"); 61 | return; 62 | } 63 | $("#newTagInput").val(""); 64 | } 65 | ]); 66 | }); 67 | 68 | $(".u-label-picker").click(function(event) { 69 | $("#newTag").attr("class", "label b-label-" + $(this).attr("value")).find("input").attr("color", $(this).attr("color")); 70 | }); 71 | 72 | $("#articleForm").submit(function(event) { 73 | var that = $(this), 74 | articleTags = $articleTags.find("span"), 75 | tags = [], 76 | i, m; 77 | formValidate.hide(); 78 | if (!this.title.value) { 79 | formValidate.text("标题不能为空").slideDown(); 80 | return false; 81 | } 82 | for (i = articleTags.length; i--;) { 83 | tags.push($(articleTags[i]).attr("tid")); 84 | } 85 | this.tags.value = JSON.stringify(tags); 86 | }); 87 | }(jQuery, window)); -------------------------------------------------------------------------------- /routes/Model/CommonDAO.js: -------------------------------------------------------------------------------- 1 | var MongoClient = require('mongodb').MongoClient, 2 | setting = require("../setting.js"), 3 | client; 4 | console.log("MongoDB connecting..."); 5 | exports.save = function(collectionName, obj, callback) { 6 | MongoClient.connect(setting.host, function(err, client) { 7 | client.collection(collectionName, function(err, collection) { 8 | if (err) return callback(err); 9 | collection.insert(obj, { 10 | safe: true 11 | }, function(err, result) { 12 | callback(err, result); 13 | client.close(); 14 | }); 15 | }); 16 | }); 17 | }; 18 | 19 | exports.findOne = function(collectionName, oArgs, callback) { 20 | MongoClient.connect(setting.host, function(err, client) { 21 | client.collection(collectionName, function(err, collection) { 22 | if (err) return callback(err); 23 | collection.findOne(oArgs, function(err, result) { 24 | callback(err, result); 25 | client.close(); 26 | }); 27 | }); 28 | }); 29 | }; 30 | 31 | exports.update = function(collectionName, oArgs, newObj, callback) { 32 | if (newObj._id) delete newObj._id; 33 | MongoClient.connect(setting.host, function(err, client) { 34 | client.collection(collectionName, function(err, collection) { 35 | if (err) return callback(err); 36 | collection.update(oArgs, { 37 | $set: newObj 38 | }, { 39 | safe: true, 40 | upsert: false, 41 | multi: true 42 | }, 43 | function(err, result) { 44 | callback(err, result); 45 | client.close(); 46 | }); 47 | }); 48 | }); 49 | 50 | }; 51 | 52 | exports.remove = function(collectionName, oArgs, callback) { 53 | MongoClient.connect(setting.host, function(err, client) { 54 | client.collection(collectionName, function(err, collection) { 55 | if (err) return callback(err); 56 | collection.remove(oArgs, function(err, result) { 57 | callback(err, result); 58 | client.close(); 59 | }); 60 | }); 61 | }); 62 | }; 63 | 64 | exports.count = function(collectionName, oArgs, callback) { 65 | MongoClient.connect(setting.host, function(err, client) { 66 | client.collection(collectionName, function(err, collection) { 67 | if (err) return callback(err); 68 | collection.count(oArgs, function(err, result) { 69 | callback(err, result); 70 | client.close(); 71 | }); 72 | }); 73 | }); 74 | }; 75 | 76 | exports.find = function(collectionName, oArgs, callback) { 77 | MongoClient.connect(setting.host, function(err, client) { 78 | client.collection(collectionName, function(err, collection) { 79 | var tmp, 80 | skip; 81 | if (err) return callback(err); 82 | tmp = collection.find(oArgs.condition); 83 | if (oArgs.sort) tmp.sort(oArgs.sort); 84 | if (oArgs.page) { 85 | skip = oArgs.page.curPage * oArgs.page.perPage; 86 | tmp.limit(oArgs.page.perPage).skip(skip); 87 | } 88 | tmp.toArray(function(err, result) { 89 | callback(err, result); 90 | client.close(); 91 | }); 92 | }); 93 | }); 94 | }; -------------------------------------------------------------------------------- /public/stylesheets/page/comment.css: -------------------------------------------------------------------------------- 1 | .b-comment-loading { 2 | background: url("/images/loading.gif") no-repeat; 3 | background-position: center; 4 | min-height: 32px; 5 | min-width: 32px; 6 | } 7 | 8 | .g-comment-row { 9 | width : 100%; 10 | } 11 | 12 | .g-comment-row .g-info { 13 | display: block; 14 | height: 30px; 15 | margin-left : 100px; 16 | line-height: 30px; 17 | } 18 | 19 | .g-comment-row .g-info .u-nick{ 20 | margin-right: 20px; 21 | font-weight : bolder; 22 | font-size: large 23 | } 24 | 25 | .g-comment-row .g-info .u-time{ 26 | float:right; 27 | } 28 | 29 | .g-comment-row .u-content { 30 | display: block; 31 | margin-left : 100px; 32 | margin-top : 10px; 33 | font-size: large 34 | } 35 | 36 | .g-comment-row .u-avatar { 37 | float: left; 38 | } 39 | 40 | .g-comment-row .g-operate { 41 | margin-left: 100px; 42 | } 43 | 44 | .g-comment-row .g-operate:after { 45 | display: table; 46 | content: ''; 47 | clear: both; 48 | } 49 | 50 | .g-comment-row .u-opt { 51 | float:right; 52 | margin-left : 10px; 53 | line-height: 14px; 54 | padding : 4px 6px; 55 | border-radius: 4px; 56 | cursor : pointer; 57 | } 58 | 59 | .g-comment-row .u-opt:hover { 60 | background-color: #f0f0f0; 61 | } 62 | 63 | .g-comment-row .admired { 64 | background-color: #f0f0f0; 65 | } 66 | 67 | .g-comment-row .u-opt span{ 68 | margin-left : 5px; 69 | } 70 | 71 | .b-comment-reply { 72 | background: #f4f4f4; 73 | padding: 10px 20px; 74 | border-radius: 20px; 75 | margin-bottom: 20px; 76 | border: 2px dashed #8F8F8F; 77 | } 78 | 79 | .u-comment-count { 80 | cursor: pointer; 81 | } 82 | 83 | .u-comment-count a:hover { 84 | color:#333; 85 | } 86 | 87 | .u-comment-count a { 88 | color:#333; 89 | text-decoration: none 90 | } 91 | 92 | .a-comment-reply:hover { 93 | -webkit-animation: borderchange 1s infinite; 94 | -moz-animation: borderchange 1s infinite; 95 | animation: borderchange 1s infinite; 96 | } 97 | 98 | @-webkit-keyframes borderchange{ 99 | 0% { 100 | border: 2px dashed #8F8F8F; 101 | } 102 | 25% { 103 | border: 2px dashed orange; 104 | } 105 | 50% { 106 | border: 2px dashed red; 107 | } 108 | 75% { 109 | border: 2px dashed blue; 110 | } 111 | 100%{ 112 | border: 2px dashed #8F8F8F; 113 | } 114 | } 115 | 116 | @-moz-keyframes borderchange{ 117 | 0% { 118 | border: 2px dashed #8F8F8F; 119 | } 120 | 25% { 121 | border: 2px dashed orange; 122 | } 123 | 50% { 124 | border: 2px dashed red; 125 | } 126 | 75% { 127 | border: 2px dashed blue; 128 | } 129 | 100%{ 130 | border: 2px dashed #8F8F8F; 131 | } 132 | } 133 | 134 | @keyframes borderchange{ 135 | 0% { 136 | border: 2px dashed #8F8F8F; 137 | } 138 | 25% { 139 | border: 2px dashed orange; 140 | } 141 | 50% { 142 | border: 2px dashed red; 143 | } 144 | 75% { 145 | border: 2px dashed blue; 146 | } 147 | 100%{ 148 | border: 2px dashed #8F8F8F; 149 | } 150 | } -------------------------------------------------------------------------------- /routes/Actions/AdmireAction.js: -------------------------------------------------------------------------------- 1 | var Admire = require("../Model/Admire.js"), 2 | Comment = require("../Model/Comment.js"), 3 | Remind = require("../Model/Remind.js"), 4 | moment = require("moment"); 5 | 6 | moment.lang("zh-cn"); 7 | 8 | exports.add = function(req, res) { 9 | var admire = new Admire({ 10 | username: req.session.user.username, 11 | commentId: req.body.commentId 12 | }); 13 | admire.save(function(err, admire) { 14 | if (err) return res.json(500, { 15 | message: err.message 16 | }); 17 | Comment.get(admire.commentId, function(err, comment) { 18 | if (err) return res.json(500, { 19 | message: err.message 20 | }); 21 | new Remind({ 22 | type: "admire", 23 | ref: admire.id, 24 | user: comment.username 25 | }).save(function(err) { 26 | if (err) return res.json(500, { 27 | message: err.message 28 | }); 29 | res.json({ 30 | success: true 31 | }); 32 | }); 33 | }); 34 | }); 35 | }; 36 | 37 | exports.remove = function(req, res) { 38 | var admire = new Admire({ 39 | username: req.session.user.username, 40 | commentId: req.body.commentId 41 | }); 42 | admire.remove(function(err) { 43 | if (err) return res.json(500, { 44 | message: err.message 45 | }); 46 | res.json({ 47 | success: true 48 | }); 49 | }); 50 | }; 51 | 52 | exports.checkAdmired = function(req, res) { 53 | var username = req.session.user.username, 54 | commentId = req.body.commentId; 55 | Admire.checkAdmired(username, commentId, function(err, admired) { 56 | if (err) return res.json(500, { 57 | message: err.message 58 | }); 59 | res.json({ 60 | admired: admired 61 | }); 62 | }); 63 | }; 64 | 65 | exports.countByComment = function(req, res) { 66 | var commentId = req.body.commentId; 67 | Admire.countByComment(commentId, function(err, total) { 68 | if (err) return res.json(500, { 69 | message: err.message 70 | }); 71 | res.json({ 72 | total: total 73 | }); 74 | }); 75 | }; 76 | 77 | exports.getOne = function(req, res) { 78 | Admire.get(req.body.admireId, function(err, admire) { 79 | if (err) return res.json(500); 80 | if (!admire) return res.status(404).send("not fount"); 81 | admire.time = moment(admire.time).fromNow(); 82 | res.json({ 83 | admire: admire 84 | }); 85 | }); 86 | }; 87 | 88 | exports.getByUser = function(req, res) { 89 | Admire.getByUser(req.body.username, Number(req.body.curPage), Number(req.body.perPage), function(err, admires) { 90 | if (err) return res.json(500); 91 | for (var i = admires.length; i--;) { 92 | admires[i].time = moment(admires[i].time).fromNow(); 93 | } 94 | res.json({ 95 | admires: admires 96 | }); 97 | }); 98 | }; 99 | 100 | exports.countByUser = function(req, res) { 101 | Admire.countByUser(req.body.username, function(err, total) { 102 | if (err) return res.json(500); 103 | res.json({ 104 | total: total 105 | }); 106 | }); 107 | }; -------------------------------------------------------------------------------- /routes/Actions/BookmarkAction.js: -------------------------------------------------------------------------------- 1 | var Bookmark = require("../Model/Bookmark.js"), 2 | Article = require("../Model/Article.js"), 3 | Remind = require("../Model/Remind.js"), 4 | moment = require("moment"); 5 | 6 | moment.lang("zh-cn"); 7 | 8 | exports.save = function(req, res) { 9 | var bookmark = new Bookmark({ 10 | articleId: req.body.articleId, 11 | username: req.session.user.username 12 | }); 13 | bookmark.save(function(err, bookmark) { 14 | if (err) return res.json(500, { 15 | message: err.message 16 | }); 17 | Article.get(bookmark.articleId, function(err, article) { 18 | if (err) return res.json(500, { 19 | message: err.message 20 | }); 21 | new Remind({ 22 | type: "bookmark", 23 | ref: bookmark.id, 24 | user: article.writer 25 | }).save(function(err, remind) { 26 | if (err) return res.json(500, { 27 | message: err.message 28 | }); 29 | res.json({ 30 | success: true 31 | }); 32 | }); 33 | }); 34 | res.json({ 35 | success: true 36 | }); 37 | }); 38 | }; 39 | 40 | exports.remove = function(req, res) { 41 | var bookmark = new Bookmark({ 42 | articleId: req.body.articleId, 43 | username: req.session.user.username 44 | }); 45 | bookmark.remove(function(err) { 46 | if (err) return res.json(500, { 47 | message: err.message 48 | }); 49 | res.json({ 50 | success: true 51 | }); 52 | }); 53 | }; 54 | 55 | exports.countByArticle = function(req, res) { 56 | Bookmark.countByArticle(req.body.articleId, function(err, total) { 57 | if (err) return res.json(500, { 58 | message: err.message 59 | }); 60 | res.json({ 61 | total: total 62 | }); 63 | }); 64 | }; 65 | 66 | exports.checkBooked = function(req, res) { 67 | Bookmark.checkBooked(req.session.user.username, req.body.articleId, function(err, booked) { 68 | if (err) return res.json(500, { 69 | message: err.message 70 | }); 71 | res.json({ 72 | booked: booked 73 | }); 74 | }); 75 | }; 76 | 77 | exports.getOne = function(req, res) { 78 | Bookmark.get(req.body.bookmarkId, function(err, bookmark) { 79 | if (err) return res.json(500); 80 | if (!bookmark) return res.status(404).send("not found"); 81 | bookmark.time = moment(bookmark.time).fromNow(); 82 | res.json({ 83 | bookmark: bookmark 84 | }); 85 | }); 86 | }; 87 | 88 | exports.getByUser = function(req, res) { 89 | Bookmark.getByUser(req.body.username, Number(req.body.curPage), Number(req.body.perPage), function(err, bookmarks) { 90 | if (err) return res.json(500); 91 | for (var i = bookmarks.length; i--;) { 92 | bookmarks[i].time = moment(bookmarks[i].time).fromNow(); 93 | } 94 | res.json({ 95 | bookmarks: bookmarks 96 | }); 97 | }); 98 | }; 99 | 100 | exports.countByUser = function(req, res) { 101 | Bookmark.countByUser(req.body.username, function(err, total) { 102 | if (err) return res.json(500); 103 | res.json({ 104 | total: total 105 | }); 106 | }); 107 | }; -------------------------------------------------------------------------------- /public/javascripts/page/search.js: -------------------------------------------------------------------------------- 1 | (function($, window) { 2 | var $tags = $("#tags"), 3 | $chosenTags = $("#chosenTags"), 4 | $articles = $("#articles"), 5 | tags = [], 6 | curPage = 0, 7 | perPage = 10, 8 | title = "", 9 | searchType, 10 | __chooseTag = function(event) { 11 | var that = $(this); 12 | $chosenTags.append(that); 13 | that.unbind("click", __chooseTag).bind("click", __unChooseTag); 14 | }, 15 | __unChooseTag = function(event) { 16 | var that = $(this); 17 | $tags.append(that); 18 | that.unbind("click", __unChooseTag).bind("click", __chooseTag); 19 | }, 20 | __drawArticle = function(article) { 21 | var $row = $('
'), 22 | $avatar = $('
'), 23 | $time = $('
').text(article.writeTime), 24 | $title = $('
').text(article.title); 25 | $row.append($avatar).append($time).append($title).click(function(event) { 26 | window.location.href = "/article_load?articleId=" + article.id; 27 | }); 28 | $(document).trigger("user.getInfo", [article.writer, 29 | function(err, user) { 30 | if (err) return; 31 | if (user) { 32 | $avatar.append('头像无法显示'); 33 | } 34 | } 35 | ]); 36 | $articles.append($row); 37 | }, 38 | __getMore = function(event) { 39 | var that = $(this), 40 | __searchCallback = function(err, articles) { 41 | if (err) { 42 | that.text("搜索发生错误"); 43 | return; 44 | } 45 | for (var i = 0, m = articles.length; i < m; i++) { 46 | __drawArticle(articles[i]); 47 | } 48 | if (articles.length < perPage) { 49 | that.text("已经没有更多的结果了"); 50 | } else { 51 | that.text("点击搜索更多").bind("click", __getMore); 52 | curPage++; 53 | } 54 | }; 55 | if (searchType === "tags") { 56 | if (tags.length > 0) { 57 | that.text("搜索中,请稍后").unbind("click", __getMore); 58 | $(document).trigger("article.getByTags", [tags, curPage, perPage, __searchCallback]); 59 | } 60 | } else if (searchType === "title") { 61 | console.log(title); 62 | if (title.trim()) { 63 | that.text("搜索中,请稍后").unbind("click", __getMore); 64 | $(document).trigger("article.getByTitle", [title, curPage, perPage, __searchCallback]); 65 | } 66 | } 67 | }; 68 | $(document).trigger("tag.drawAllTags", [$tags, __chooseTag]); 69 | $("#more").click(__getMore); 70 | 71 | $("#tagSearchBtn").click(function(event) { 72 | $articles.html(""); 73 | tags = []; 74 | $chosenTags.find(".label").each(function() { 75 | tags.push($(this).attr("tid")); 76 | }); 77 | curPage = 0; 78 | searchType = "tags"; 79 | $("#more").unbind("click", __getMore).bind("click", __getMore).click(); 80 | }); 81 | 82 | $("#titleSearchBtn").click(function(event) { 83 | $articles.html(""); 84 | title = $("#titleSearchInput").val(); 85 | curPage = 0; 86 | searchType = "title"; 87 | $("#more").unbind("click", __getMore).bind("click", __getMore).click(); 88 | }); 89 | }(jQuery, window)); -------------------------------------------------------------------------------- /public/stylesheets/page/gallary.css: -------------------------------------------------------------------------------- 1 | .u-pic { 2 | position: relative; 3 | border: 1px solid #f0f0f0; 4 | box-shadow: 0 0 6px #f0f0f0; 5 | display: block; 6 | cursor: pointer; 7 | } 8 | 9 | .u-pic img { 10 | width: 100%; 11 | } 12 | 13 | .u-pic:hover { 14 | position: relative; 15 | z-index: 100; 16 | -webkit-animation : bigger 1s 1; 17 | -webkit-transform: scale(1.25); 18 | -moz-animation : bigger 1s 1; 19 | -moz-transform: scale(1.25); 20 | animation : bigger 1s 1; 21 | transform: scale(1.25); 22 | } 23 | 24 | @-moz-keyframes bigger { /* firefox */ 25 | from { 26 | -moz-transform: scale(1); 27 | } 28 | to { 29 | -moz-transform: scale(1.25); 30 | } 31 | } 32 | 33 | @-webkit-keyframes bigger { /*chrome & safari*/ 34 | from { 35 | -webkit-transform: scale(1); 36 | } 37 | to { 38 | -webkit-transform: scale(1.25); 39 | } 40 | } 41 | 42 | @keyframes bigger { 43 | from { 44 | transform: scale(1); 45 | } 46 | to { 47 | transform: scale(1.25); 48 | } 49 | } 50 | 51 | 52 | .scrollFooter { 53 | width: 100%; 54 | border-radius: 10px; 55 | height: 40px; 56 | line-height: 35px; 57 | text-align: center; 58 | background-color: #f0f0f0; 59 | display: none 60 | } 61 | 62 | .g-gal-col { 63 | padding: 0 64 | } 65 | 66 | .u-block { 67 | position: fixed; 68 | width: 100%; 69 | height: 100%; 70 | left : 0; 71 | top : 0; 72 | background-color: rgba(240, 240, 240, 0.7); 73 | z-index: 101; 74 | display: none; 75 | overflow: auto; 76 | } 77 | 78 | .u-fillPic { 79 | position: absolute; 80 | margin-left: 50%; 81 | border: 1px solid #818181; 82 | box-shadow: 3px 3px 8px #818181; 83 | z-index: 200; 84 | display: none; 85 | width: 70%; 86 | left: -35%; 87 | background-color: #f0f0f0; 88 | margin-top : 50px; 89 | } 90 | 91 | .u-fillPic .u-opt { 92 | position : absolute; 93 | width: 36px; 94 | height: 36px; 95 | font-size: 20px; 96 | line-height: 36px; 97 | text-align: center; 98 | background-color: #f5f5f5; 99 | color : #818181; 100 | border: 1px solid #818181; 101 | border-radius: 18px; 102 | box-shadow: 3px 3px 8px #818181; 103 | } 104 | 105 | .u-fillPic .u-opt:hover { 106 | background-color: #818181; 107 | color : white; 108 | } 109 | 110 | .u-close { 111 | top : -16px; 112 | right : -16px; 113 | } 114 | 115 | .u-bigger{ 116 | top : 24px; 117 | right : -16px; 118 | } 119 | 120 | .u-smaller{ 121 | top : 64px; 122 | right : -16px; 123 | } 124 | 125 | .u-fillPic img{ 126 | max-width: 100%; 127 | min-width: 300px; 128 | } 129 | 130 | .u-conf-opt { 131 | position: fixed; 132 | width: 100px; 133 | height: 100px; 134 | text-align: center; 135 | line-height: 85px; 136 | font-size: 60px; 137 | border: 4px dotted #333; 138 | border-radius: 20px; 139 | } 140 | 141 | .u-trash { 142 | right: 10px; 143 | top : 100px; 144 | } 145 | 146 | .u-upload { 147 | left: 10px; 148 | top : 100px; 149 | } 150 | 151 | .b-removing { 152 | width: 100%; 153 | height: 100%; 154 | position: absolute; 155 | background-color: rgba(0,0,0,0.7); 156 | left: 0; 157 | top: 0; 158 | text-align: center; 159 | font-size: 50px; 160 | color: #f0f0f0; 161 | font-weight: bolder; 162 | } -------------------------------------------------------------------------------- /routes/Model/Remind.js: -------------------------------------------------------------------------------- 1 | var commonDao = require("./CommonDAO.js"), 2 | collectionName = "remind", 3 | uuid = require("node-uuid"), 4 | async = require("async"), 5 | __resultToListFn = function(callback) { 6 | return function(err, results) { 7 | var i; 8 | if (err) return callback(err); 9 | for (i = results.length; i--;) { 10 | results[i] = new Remind(results[i]); 11 | } 12 | callback(err, results); 13 | }; 14 | }; 15 | 16 | function Remind(remind) { 17 | this.id = remind.id; 18 | this.time = remind.time; 19 | this.type = remind.type; 20 | this.ref = remind.ref; 21 | this.readed = remind.readed; 22 | this.user = remind.user; 23 | } 24 | 25 | module.exports = Remind; 26 | 27 | Remind.prototype.save = function(callback) { 28 | commonDao.save(collectionName, { 29 | time: new Date().getTime(), 30 | type: this.type, 31 | ref: this.ref, 32 | user: this.user, 33 | readed: false, 34 | id: uuid.v4() 35 | }, function(err, result) { 36 | if (err) return callback(err); 37 | if (!result[0]) return callback(new Error("保存失败")); 38 | callback(err, new Remind(result[0])); 39 | }); 40 | }; 41 | 42 | Remind.getOne = function(remindId, callback) { 43 | commonDao.findOne(collectionName, { 44 | id: remindId 45 | }, function(err, result) { 46 | if (err) return callback(err); 47 | callback(err, result ? new Remind(result) : null); 48 | }); 49 | }; 50 | 51 | Remind.prototype.remove = function(callback) { 52 | commonDao.remove(collectionName, { 53 | id: this.id 54 | }, callback); 55 | }; 56 | 57 | Remind.prototype.update = function(callback) { 58 | commonDao.update(collectionName, { 59 | id: this.id 60 | }, { 61 | readed: this.readed 62 | }, callback); 63 | }; 64 | 65 | Remind.getByUser = function(username, curPage, perPage, callback) { 66 | commonDao.find(collectionName, { 67 | condition: { 68 | user: username 69 | }, 70 | sort: { 71 | time: -1 72 | }, 73 | page: { 74 | curPage: curPage, 75 | perPage: perPage 76 | }, 77 | }, __resultToListFn(callback)); 78 | }; 79 | 80 | Remind.getByUserAndType = function(username, type, curPage, perPage, callback) { 81 | commonDao.find(collectionName, { 82 | condition: { 83 | user: username, 84 | type: type 85 | }, 86 | page: { 87 | curPage: curPage, 88 | perPage: perPage 89 | }, 90 | sort: { 91 | time: -1 92 | } 93 | }, __resultToListFn(callback)); 94 | }; 95 | 96 | Remind.countByUser = function(username, callback) { 97 | commonDao.count(collectionName, { 98 | user: username 99 | }, callback); 100 | }; 101 | 102 | Remind.countByUserAndType = function(username, type, callback) { 103 | commonDao.count(collectionName, { 104 | user: username, 105 | type: type 106 | }, callback); 107 | }; 108 | 109 | Remind.countUnreadByUser = function(username, callback) { 110 | commonDao.count(collectionName, { 111 | user: username, 112 | readed: false 113 | }, callback); 114 | }; 115 | 116 | Remind.countUnreadByUserAndType = function(username, type, callback) { 117 | commonDao.count(collectionName, { 118 | user: username, 119 | type: type, 120 | readed: false 121 | }, callback); 122 | }; -------------------------------------------------------------------------------- /public/stylesheets/pageTools/comment.css: -------------------------------------------------------------------------------- 1 | @media (max-width: 992px) { 2 | div.b-comment-reply { 3 | padding: 5px 10px; 4 | } 5 | } 6 | 7 | .b-comment-loading { 8 | background: url("/images/loading.gif") no-repeat; 9 | background-position: center; 10 | min-height: 32px; 11 | min-width: 32px; 12 | } 13 | 14 | .g-comment-row { 15 | width : 100%; 16 | font-size: 14px; 17 | } 18 | 19 | .g-comment-row .g-info { 20 | display: block; 21 | height: 30px; 22 | margin-left : 100px; 23 | line-height: 30px; 24 | } 25 | 26 | .g-comment-row .g-info:after{ 27 | display: table; 28 | content: ""; 29 | clear: both; 30 | } 31 | 32 | 33 | .g-comment-row .g-info .u-nick{ 34 | float: left; 35 | width: 50%; 36 | font-weight : bolder; 37 | font-size: large 38 | } 39 | 40 | .g-comment-row .g-info .u-time{ 41 | width: 50%; 42 | float:right; 43 | text-align: right; 44 | } 45 | 46 | .g-comment-row .u-content { 47 | display: block; 48 | margin-left : 100px; 49 | margin-top : 10px; 50 | font-size: large 51 | } 52 | 53 | .g-comment-row .u-avatar { 54 | float: left; 55 | } 56 | 57 | .g-comment-row .g-operate { 58 | margin-left: 100px; 59 | height: 25px; 60 | } 61 | 62 | .g-comment-row .g-operate:after { 63 | display: table; 64 | content: ''; 65 | clear: both; 66 | } 67 | 68 | .g-comment-row .u-opt { 69 | float:right; 70 | margin-left : 10px; 71 | line-height: 14px; 72 | padding : 4px 6px; 73 | border-radius: 4px; 74 | cursor : pointer; 75 | } 76 | 77 | .g-comment-row .u-opt:hover, div.g-comment-row .admired{ 78 | background-color: rgba(66, 139, 202, 0.7); 79 | color: white; 80 | } 81 | 82 | .g-comment-row .u-opt{ 83 | text-decoration: none; 84 | color:#333; 85 | } 86 | 87 | .g-comment-row .u-opt span{ 88 | margin-left : 5px; 89 | } 90 | 91 | .b-comment-reply { 92 | background: #f4f4f4; 93 | padding: 10px 20px; 94 | border-radius: 20px; 95 | margin-bottom: 20px; 96 | border: 2px dashed #8F8F8F; 97 | } 98 | 99 | .u-comment-count { 100 | cursor: pointer; 101 | } 102 | 103 | .u-comment-count a:hover { 104 | color:#333; 105 | } 106 | 107 | .u-comment-count a { 108 | color:#333; 109 | text-decoration: none 110 | } 111 | 112 | .a-comment-reply:hover { 113 | -webkit-animation: borderchange 1s infinite; 114 | -moz-animation: borderchange 1s infinite; 115 | animation: borderchange 1s infinite; 116 | } 117 | 118 | @-webkit-keyframes borderchange{ 119 | 0% { 120 | border: 2px dashed #8F8F8F; 121 | } 122 | 25% { 123 | border: 2px dashed orange; 124 | } 125 | 50% { 126 | border: 2px dashed red; 127 | } 128 | 75% { 129 | border: 2px dashed blue; 130 | } 131 | 100%{ 132 | border: 2px dashed #8F8F8F; 133 | } 134 | } 135 | 136 | @-moz-keyframes borderchange{ 137 | 0% { 138 | border: 2px dashed #8F8F8F; 139 | } 140 | 25% { 141 | border: 2px dashed orange; 142 | } 143 | 50% { 144 | border: 2px dashed red; 145 | } 146 | 75% { 147 | border: 2px dashed blue; 148 | } 149 | 100%{ 150 | border: 2px dashed #8F8F8F; 151 | } 152 | } 153 | 154 | @keyframes borderchange{ 155 | 0% { 156 | border: 2px dashed #8F8F8F; 157 | } 158 | 25% { 159 | border: 2px dashed orange; 160 | } 161 | 50% { 162 | border: 2px dashed red; 163 | } 164 | 75% { 165 | border: 2px dashed blue; 166 | } 167 | 100%{ 168 | border: 2px dashed #8F8F8F; 169 | } 170 | } -------------------------------------------------------------------------------- /public/javascripts/page/editArticle.js: -------------------------------------------------------------------------------- 1 | (function($, window) { 2 | var converter = new Showdown.converter(), 3 | formValidate = $("#formValidate"), 4 | $articleTags = $("#articleTags"), 5 | $allTags = $("#allTags"), 6 | getWindowHeight = function() { 7 | if (window.innerHeight) 8 | return window.innerHeight; 9 | else if (document.documentElement && document.documentElement.clientHeight) 10 | return document.documentElement.clientHeight; 11 | else if (document.body) 12 | return document.body.clientHeight; 13 | }, 14 | __addToArticleTags = function(event) { 15 | var tag = event.data, 16 | $div = $("
" + tag.name + "
"), 17 | $existTag = $articleTags.find("span[tid='" + tag.id + "']"); 18 | $div.click(__removeFromArticleTags); 19 | if ($existTag.length === 0) { 20 | $articleTags.append($div); 21 | } else { 22 | $existTag.parent().removeClass("a-label-strike"); 23 | setTimeout(function() { 24 | $existTag.parent().addClass("a-label-strike"); 25 | }, 0); 26 | } 27 | }, 28 | __removeFromArticleTags = function(event) { 29 | $(this).remove(); 30 | }; 31 | 32 | $("#inputArticle").bind("keyup", function(event) { 33 | $("#outputArticle").html(converter.makeHtml(this.value)); 34 | $('pre code').each(function(i, e) { 35 | hljs.highlightBlock(e); 36 | }); 37 | }).trigger("change"); 38 | 39 | $(window).bind("resize", function(event) { 40 | $("body").height(getWindowHeight() - 150); 41 | $(".panel").height(getWindowHeight() - 350); 42 | }).trigger("resize"); 43 | 44 | $("#saveArtBtn").click(function(event) { 45 | var that = $(this), 46 | articleTags = $articleTags.find("span"), 47 | tags = [], 48 | i, m; 49 | formValidate.hide(); 50 | if (!$("#inputTitle").val() || $("#inputTitle").val() === "") { 51 | formValidate.text("标题不能为空").slideDown(); 52 | return false; 53 | } 54 | for (i = articleTags.length; i--;) { 55 | tags.push($(articleTags[i]).attr("tid")); 56 | } 57 | $.ajax({ 58 | url: "/nor/conf/article_update", 59 | data: { 60 | articleId: that.attr("aid"), 61 | title: $("#inputTitle").val(), 62 | content: $("#inputArticle").val(), 63 | tags: JSON.stringify(tags) 64 | }, 65 | type: "POST", 66 | dataType: "json" 67 | }).done(function(data) { 68 | window.location.href = "/article_load?articleId=" + that.attr("aid"); 69 | }).error(function(data) { 70 | console.log(data); 71 | }); 72 | }); 73 | 74 | $(document).trigger("tag.drawAllTags", [$allTags, __addToArticleTags]); 75 | 76 | $(document).trigger("tag.drawArticleTags", [$articleTags.attr("aid"), $articleTags, __removeFromArticleTags]); 77 | 78 | $("#createTag").click(function(event) { 79 | var name = $("#newTagInput").val(), 80 | color = $("#newTagInput").attr("color"); 81 | if (!name) { 82 | $("#newTagInput").addClass("has-error"); 83 | return false; 84 | } 85 | 86 | $(document).trigger("tag.createTag", [name, color, $allTags, __addToArticleTags, 87 | function(event) { 88 | $("#newTagInput").val(""); 89 | } 90 | ]); 91 | }); 92 | 93 | $(".u-label-picker").click(function(event) { 94 | $("#newTag").attr("class", "label b-label-" + $(this).attr("value")).find("input").attr("color", $(this).attr("color")); 95 | }); 96 | 97 | }(jQuery, window)); -------------------------------------------------------------------------------- /public/404/404.css: -------------------------------------------------------------------------------- 1 | .main { 2 | position: absolute; 3 | width: 800px; 4 | height: 500px; 5 | margin-top:100px; 6 | left: 50%; 7 | margin-left: -400px; 8 | background-image: -webkit-linear-gradient(top, #5CC9FC, #FFFFFF); 9 | border-radius: 10px; 10 | overflow: hidden; 11 | box-shadow : 5px 5px 6px #525252; 12 | 13 | } 14 | 15 | .clouds { 16 | position: absolute; 17 | -webkit-animation: cloud_move 20s linear infinite; 18 | } 19 | 20 | .clouds .cloud { 21 | position: absolute; 22 | background-image: url("cloud.png"); 23 | background-position: center; 24 | background-repeat: no-repeat; 25 | } 26 | 27 | .clouds .cloud-sm{ 28 | width: 223px; 29 | height: 98px; 30 | background-size: 223px 78px; 31 | } 32 | 33 | .clouds .cloud-md{ 34 | width: 335px; 35 | height: 147px; 36 | background-size: 335px 147px; 37 | } 38 | 39 | .clouds .cloud-lg{ 40 | width: 670px; 41 | height: 294px; 42 | background-size: 670px 294px; 43 | } 44 | 45 | .boat { 46 | position: absolute; 47 | width: 100%; 48 | height: 100%; 49 | -webkit-animation: boat_shake 5s linear infinite; 50 | } 51 | 52 | .boat .boat-body{ 53 | position: absolute; 54 | bottom: 20px; 55 | background-position: center; 56 | background-repeat: no-repeat; 57 | background-image: url(boat.png); 58 | width: 384px; 59 | height: 354px; 60 | -webkit-transform: scaleX(-1); 61 | } 62 | 63 | .wave { 64 | position: absolute; 65 | width: 900px; 66 | bottom: -20px; 67 | left: -50px; 68 | } 69 | 70 | .wave-front { 71 | height: 120px; 72 | background-image: url(wave-front.png); 73 | -webkit-animation: wave_shake 6s linear infinite; 74 | } 75 | 76 | .wave-back { 77 | height: 125px; 78 | background-image: url(wave-back.png); 79 | -webkit-animation: wave_shake 6s linear 3s infinite; 80 | } 81 | 82 | .message { 83 | position: absolute; 84 | left: 450px; 85 | top: 100px; 86 | width: 300px; 87 | height: 200px; 88 | text-align: center; 89 | font-family: "Microsoft YaHei"; 90 | font-weight: bolder; 91 | color: rgb(109, 109, 109); 92 | text-shadow : 2px 2px 2px #ACACAC; 93 | } 94 | 95 | .message .code { 96 | font-size: 80px; 97 | } 98 | 99 | /* .message .code span:hover { 100 | display: block; 101 | -webkit-transform: rotate(90deg); 102 | -webkit-transform-origin: 20% 47%; 103 | -webkit-animation: error_code_down 2s ease-in-out 1; 104 | } */ 105 | 106 | .message .content { 107 | font-size: 40px; 108 | } 109 | @-webkit-keyframes error_code_down { 110 | from { 111 | -webkit-transform: rotate(0deg); 112 | } 113 | to { 114 | -webkit-transform: rotate(90deg); 115 | } 116 | } 117 | 118 | @-webkit-keyframes wave_shake { 119 | 0% { 120 | -webkit-transform: translate(0px, 0px); 121 | } 122 | 25% { 123 | -webkit-transform: translate(5px, 5px); 124 | } 125 | 75% { 126 | -webkit-transform: translate(0px, 5px); 127 | } 128 | 100% { 129 | -webkit-transform: translate(0px, 0px); 130 | } 131 | } 132 | 133 | @-webkit-keyframes cloud_move { 134 | from { 135 | left: -800px; 136 | } 137 | to { 138 | left: 0px; 139 | } 140 | } 141 | 142 | @-webkit-keyframes boat_shake { 143 | 0% { 144 | -webkit-transform: translate(0px, 0px); 145 | } 146 | 25% { 147 | -webkit-transform: translate(-10px, -10px); 148 | } 149 | 75% { 150 | -webkit-transform: translate(0px, -10px); 151 | } 152 | 100% { 153 | -webkit-transform: translate(0px, 0px); 154 | } 155 | } -------------------------------------------------------------------------------- /public/javascripts/page/uploadPic.js: -------------------------------------------------------------------------------- 1 | (function($, window) { 2 | var $uploadZone = $("#uploadZone"), 3 | $uploadOutput = $("#uploadOutput"), 4 | $uploadResult = $("#uploadResult"), 5 | __uploadCancel = function(event) { 6 | var that = $(this).parent(), 7 | fileName = that.attr("fileName"); 8 | $.ajax({ 9 | url: "/nor/conf/picture_uploadCancel", 10 | data: { 11 | fileName: fileName 12 | }, 13 | dataType: "json", 14 | type: "post" 15 | }).done(function(data) { 16 | that.fadeOut(function() { 17 | $(this).remove(); 18 | }); 19 | }).fail(function(err) { 20 | console.log(err); 21 | }); 22 | }, 23 | __uploadFile = function(file) { 24 | var $div = $("
"), 25 | reader = new FileReader(), 26 | form = new FormData(), 27 | xhr = new XMLHttpRequest(); 28 | $uploadOutput.prepend($div); 29 | reader.onload = __parseCallback($div); 30 | reader.readAsDataURL(file); 31 | form.append("image", file); 32 | xhr.open("post", "/nor/conf/picture_upload", true); 33 | xhr.onreadystatechange = function(event) { 34 | if (4 == this.readyState) { 35 | var data = JSON.parse(event.target.response); 36 | $div.attr("fileName", data.fileName); 37 | $div.find(".u-remove").click(__uploadCancel); 38 | } 39 | }; 40 | xhr.upload.addEventListener("progress", function(event) { 41 | $div.find(".u-process").text(event.loaded / event.total * 100 + "%"); 42 | }, false); 43 | xhr.send(form); 44 | }, 45 | __parseCallback = function($div) { 46 | return function(event) { 47 | $div.find("img").attr("src", event.target.result); 48 | }; 49 | }; 50 | 51 | $.event.props.push("dataTransfer"); 52 | 53 | $uploadZone.bind("dragover", function(event) { 54 | event.stopPropagation(); 55 | event.preventDefault(); 56 | event.dataTransfer.dropEffect = 'copy'; 57 | }); 58 | 59 | $uploadZone.bind("drop", function(event) { 60 | event.stopPropagation(); 61 | event.preventDefault(); 62 | var files = event.dataTransfer.files; 63 | 64 | for (var i = files.length; i--;) { 65 | if (!files[i].type.match(/image*/)) { 66 | continue; 67 | } 68 | __uploadFile(files[i]); 69 | } 70 | }); 71 | 72 | $("#uploadBtn").bind("click", function(event) { 73 | var imgs = []; 74 | $uploadOutput.find("div").each(function() { 75 | if ($(this).attr("fileName")) { 76 | imgs.push($(this).attr("fileName")); 77 | } 78 | }); 79 | $uploadResult.removeClass("alert-success alert-danger").hide(); 80 | if (imgs.length > 0) { 81 | $.ajax({ 82 | url: "/nor/conf/picture_confirm", 83 | type: "post", 84 | dataType: "json", 85 | data: { 86 | images: imgs 87 | } 88 | }).done(function(data) { 89 | $uploadOutput.find(".g-preview").fadeOut(function() { 90 | $uploadOutput.find(".g-preview").remove(); 91 | }); 92 | $uploadResult.addClass("alert-success").text("上传" + imgs.length + "张图片成功!").slideDown(); 93 | }).fail(function(err) { 94 | $uploadResult.addClass("alert-danger").text("上传图片失败!").slideDown(); 95 | }); 96 | } else { 97 | $uploadResult.addClass("alert-danger").text("没有选择图片!").slideDown(); 98 | } 99 | }); 100 | }(jQuery, window)); -------------------------------------------------------------------------------- /public/stylesheets/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-size: 14px; 3 | padding-top: 50px; 4 | margin-top:-50px; 5 | font-family: ff-tisa-web-pro-1, ff-tisa-web-pro-2, 'Lucida Grande', 'Helvetica Neue', Helvetica, Arial, 'Hiragino Sans GB', 'Hiragino Sans GB W3', 'Microsoft YaHei UI', 'Microsoft YaHei', 'WenQuanYi Micro Hei', sans-serif; 6 | } 7 | 8 | h1,h2,h3 { 9 | font-family: ff-tisa-web-pro-1, ff-tisa-web-pro-2, 'Lucida Grande', 'Helvetica Neue', Helvetica, Arial, 'Hiragino Sans GB', 'Hiragino Sans GB W3', 'Microsoft YaHei UI', 'Microsoft YaHei', 'WenQuanYi Micro Hei', sans-serif; 10 | } 11 | 12 | .b-loading { 13 | background: url("/images/loading.gif") no-repeat; 14 | background-position: center; 15 | min-height: 32px; 16 | min-width: 32px; 17 | } 18 | 19 | 20 | .footer hr{ 21 | border-top: 1px solid #383838; 22 | } 23 | 24 | .footer p{ 25 | text-align : center; 26 | font-style : italic; 27 | } 28 | 29 | .b-clouds { 30 | z-index: -1; 31 | position: fixed; 32 | width: 100%; 33 | height: 100%; 34 | background-image: -webkit-linear-gradient(top, #5CC9FC, #FFFFFF); 35 | background-image: -moz-linear-gradient(top, #5CC9FC, #FFFFFF); 36 | background-image: -ms-linear-gradient(top, #5CC9FC, #FFFFFF); 37 | overflow: hidden; 38 | } 39 | 40 | .b-clouds .block { 41 | position: absolute; 42 | background-image: -webkit-linear-gradient(left, #ffffff, transparent, #ffffff); 43 | background-image: -moz-linear-gradient(to right, rgba(255,255,255,1), rgba(255,255,255,0), rgba(255,255,255,1)); 44 | background-image: linear-gradient(left, white, transparent, white); 45 | background-image: -ms-linear-gradient(left, #ffffff00, transparent, #ffffff00); 46 | overflow: hidden; 47 | width: 100%; 48 | height: 100%; 49 | } 50 | 51 | .b-clouds .clouds { 52 | position: absolute; 53 | width: 200%; 54 | height: 400px; 55 | /* -webkit-animation: cloud_move 60s linear infinite; 56 | -moz-animation: cloud_move 60s linear infinite; */ 57 | } 58 | 59 | .b-clouds .clouds .cloud { 60 | position: absolute; 61 | background-image: url("/images/cloud.png"); 62 | background-position: center; 63 | background-repeat: no-repeat; 64 | } 65 | 66 | .b-clouds .clouds .cloud-sm{ 67 | width: 223px; 68 | height: 98px; 69 | background-size: 223px 78px; 70 | } 71 | 72 | .b-clouds .clouds .cloud-md{ 73 | width: 335px; 74 | height: 147px; 75 | background-size: 335px 147px; 76 | } 77 | 78 | .b-clouds .clouds .cloud-lg{ 79 | width: 670px; 80 | height: 294px; 81 | background-size: 670px 294px; 82 | } 83 | 84 | @-webkit-keyframes cloud_move { 85 | from { 86 | left: -100%; 87 | } 88 | to { 89 | left: 0; 90 | } 91 | } 92 | 93 | @-moz-keyframes cloud_move { 94 | from { 95 | left: -100%; 96 | } 97 | to { 98 | left: 0; 99 | } 100 | } 101 | 102 | @-ms-keyframes cloud_move { 103 | from { 104 | left: -100%; 105 | } 106 | to { 107 | left: 0; 108 | } 109 | } 110 | 111 | .scrollToTop { 112 | width: 100px; 113 | height: 100px; 114 | border-radius: 20px; 115 | font-size: 80px; 116 | line-height: 100px; 117 | background-color: #6D6D6D; 118 | color: white; 119 | opacity: 0.5; 120 | text-align: center; 121 | position: fixed; 122 | right: 80px; 123 | bottom: 80px; 124 | padding-right: 5px; 125 | cursor: pointer; 126 | } 127 | 128 | .scrollToTop:hover{ 129 | opacity: 1; 130 | } 131 | 132 | .g-main { 133 | margin-top: 50px; 134 | } 135 | 136 | .u-panel { 137 | padding: 20px; 138 | border-radius: 20px; 139 | box-shadow: 3px 3px 6px #8A8A8A; 140 | background: rgba(255,255,255,0.8); 141 | } 142 | -------------------------------------------------------------------------------- /routes/Actions/UserAction.js: -------------------------------------------------------------------------------- 1 | var User = require("../Model/User.js"), 2 | async = require("async"), 3 | Bookmark = require("../Model/Bookmark.js"), 4 | Admire = require("../Model/Admire.js"), 5 | Article = require("../Model/Article.js"), 6 | Comment = require("../Model/Comment.js"), 7 | moment = require("moment"); 8 | exports.loginPage = function(req, res) { 9 | res.render('login'); 10 | }; 11 | 12 | exports.registPage = function(req, res) { 13 | res.render('regist'); 14 | }; 15 | 16 | exports.login = function(req, res) { 17 | User.get(req.body.username, function(err, user) { 18 | if (err) return req.render("error", { 19 | message: "发生错误,请稍后重试..." 20 | }); 21 | if (user && user.password === req.body.password) { 22 | req.session.user = user; 23 | res.redirect("/index"); 24 | } else { 25 | res.render("login", { 26 | message: "用户名或密码错误..." 27 | }); 28 | } 29 | }); 30 | }; 31 | 32 | exports.logout = function(req, res) { 33 | if (typeof req.session.user !== "undefined") { 34 | delete req.session.user; 35 | } 36 | res.redirect("index"); 37 | }; 38 | 39 | exports.loadDetail = function(req, res) { 40 | res.render("userDetail", { 41 | user: req.session.user 42 | }); 43 | }; 44 | 45 | exports.getDetail = function(req, res) { 46 | User.get(req.body.username, function(err, user) { 47 | if (err) return res.render("error", { 48 | message: err.message 49 | }); 50 | res.json({ 51 | username: user.username, 52 | nickname: user.nickname, 53 | owner: user.owner, 54 | tags: user.tags, 55 | avatar: user.avatar 56 | }); 57 | }); 58 | }; 59 | 60 | exports.modify = function(req, res) { 61 | var tags = JSON.parse(req.body.tags); 62 | User.get(req.session.user.username, function(err, user) { 63 | if (err) return res.render("error", { 64 | message: err.message 65 | }); 66 | if (user) { 67 | user.nickname = req.body.nickname; 68 | user.tags = tags; 69 | if (req.body.password) { 70 | user.password = req.body.password; 71 | } 72 | user.avatar = req.body.avatar || "/images/default_avatar.jpg"; 73 | user.update(function(err) { 74 | req.session.user = user; 75 | res.render("userDetail", { 76 | user: req.session.user, 77 | success: true, 78 | message: "修改成功..." 79 | }); 80 | }); 81 | } else { 82 | res.render("userDetail", { 83 | user: req.session.user, 84 | success: false, 85 | message: "修改失败,用户不存在..." 86 | }); 87 | } 88 | }); 89 | }; 90 | 91 | exports.regist = function(req, res) { 92 | User.get(req.body.username, function(err, user) { 93 | if (err) return res.render("error", { 94 | message: err.message 95 | }); 96 | if (user) { 97 | return res.render("regist", { 98 | message: "用户名已被注册..." 99 | }); 100 | } else { 101 | user = new User({ 102 | username: req.body.username, 103 | password: req.body.password, 104 | nickname: req.body.nickname, 105 | avatar: "/images/default_avatar.jpg", 106 | owner: false, 107 | tags: [] 108 | }); 109 | user.save(function(err) { 110 | if (err) { 111 | return res.render("regist", { 112 | message: "发生错误,请稍后重试..." 113 | }); 114 | } else { 115 | req.session.user = user; 116 | res.redirect("/index"); 117 | } 118 | }); 119 | } 120 | }); 121 | }; -------------------------------------------------------------------------------- /routes/Actions/PictureAction.js: -------------------------------------------------------------------------------- 1 | var setting = require("../setting.js"), 2 | fs = require("fs"), 3 | async = require("async"), 4 | gm = require("gm"), 5 | path = require("path"), 6 | imageMagick = gm.subClass({ 7 | imageMagick: true 8 | }), 9 | uuid = require("node-uuid"), 10 | util = require("util"), 11 | __copyFile = function copyFile(file, toDir, callback) { 12 | var reads = fs.createReadStream(file); 13 | var writes = fs.createWriteStream(path.join(path.dirname(toDir), path.basename(file))); 14 | reads.pipe(writes); 15 | //don't forget close the when all the data are read 16 | reads.on("end", function() { 17 | writes.end(); 18 | callback(null); 19 | }); 20 | reads.on("error", function(err) { 21 | console.log("error occur in reads"); 22 | callback(true, err); 23 | }); 24 | }; 25 | 26 | exports.remove = function(req, res) { 27 | var fileName = req.body.fileName, 28 | smallFile = "public/" + setting.gallary.small + "/" + fileName, 29 | orignFile = "public/" + setting.gallary.name + "/" + fileName; 30 | try { 31 | fs.unlinkSync(smallFile); 32 | fs.unlinkSync(orignFile); 33 | } catch (e) { 34 | return res.json(500, { 35 | message: e.message 36 | }); 37 | } 38 | res.json({ 39 | success: true 40 | }); 41 | 42 | }; 43 | 44 | exports.uploadDirect = function(req, res) { 45 | var inputFile = req.files.image.path, 46 | fileName = new RegExp('^' + setting.uploadDir + '\\\\([\\w-\\.]+)$').exec(inputFile)[1], 47 | resizeFile = "public/" + setting.gallary.small + "/" + fileName, 48 | outputFile = "public/" + setting.gallary.name + "/" + fileName; 49 | async.waterfall([ 50 | 51 | function(callback) { 52 | imageMagick(inputFile).write(outputFile, function(err) { 53 | if (err) return callback(err); 54 | callback(null); 55 | }); 56 | }, 57 | function(callback) { 58 | imageMagick(inputFile).resize(400).write(resizeFile, function(err) { 59 | if (err) return callback(err); 60 | callback(null); 61 | }); 62 | }, 63 | function(callback) { 64 | if (fs.existsSync(inputFile)) { 65 | fs.unlink(inputFile, function(err) { 66 | if (err) return callback(err); 67 | callback(null); 68 | }); 69 | } else { 70 | callback(new Error("未找到文件")); 71 | } 72 | } 73 | ], function(err) { 74 | if (err) return res.json(500, { 75 | message: err.message 76 | }); 77 | res.json({ 78 | success: true, 79 | fileName: fileName, 80 | gallary: setting.gallary.name, 81 | gallary_small: setting.gallary.small 82 | }); 83 | }); 84 | }; 85 | 86 | exports.uploadDirectNoCompress = function(req, res) { 87 | var inputFile = req.files.image.path, 88 | fileName = (/^upload_tmp\/([\w\-\.]+)$/.exec(inputFile) || /^upload_tmp\\([\w\-\.]+)$/.exec(inputFile))[1], 89 | resizeFile = "public/" + setting.gallary.small + "/" + fileName, 90 | outputFile = "public/" + setting.gallary.name + "/" + fileName; 91 | async.waterfall([ 92 | 93 | function(callback) { 94 | __copyFile(inputFile, resizeFile, callback); 95 | }, 96 | function(callback) { 97 | __copyFile(inputFile, outputFile, callback); 98 | }, 99 | function(callback) { 100 | if (fs.existsSync(inputFile)) { 101 | fs.unlink(inputFile, function(err) { 102 | if (err) return callback(err); 103 | callback(null); 104 | }); 105 | } else { 106 | callback(new Error("未找到文件")); 107 | } 108 | } 109 | ], function(err) { 110 | if (err) return res.json(500, { 111 | message: err.message 112 | }); 113 | res.json({ 114 | success: true, 115 | fileName: fileName, 116 | gallary: setting.gallary.name, 117 | gallary_small: setting.gallary.small 118 | }); 119 | }); 120 | }; 121 | -------------------------------------------------------------------------------- /doc/user.md: -------------------------------------------------------------------------------- 1 | 用户 [User] 2 | === 3 | *** 4 | #构造函数 5 | ```js 6 | function User(user) { 7 | this.username = user.username; 8 | this.nickname = user.nickname; 9 | this.password = user.password; 10 | this.owner = user.owner; 11 | this.avatar = user.avatar; 12 | this.tags = user.tags; 13 | } 14 | ``` 15 | # 属性 16 | *** 17 | > **username** [string] : 用户名,需手动保持唯一 18 | 19 | > **nickname** [string] : 用户昵称 20 | 21 | > **password** [string] : 用户密码 22 | 23 | > **owner** [boolean] : 用户是否为博客管理员 24 | 25 | > **avatar** [string] : 用户头像的url 26 | 27 | > **tags** [array:string] : 用户的标签列表,元素为标签的id 28 | 29 | # 原型上的方法 30 | *** 31 | ## save 32 | ### 说明 33 | > 保存当前用户 34 | 35 | ### 参数 36 | > **callback** [function] : 保存完成后的回调函数,两个参数 37 | 38 | >> **err** [object]: 若未发生错误,为null 39 | 40 | >> **result** [Article] :持久化后的User对象 41 | 42 | ### 实例 43 | ```js 44 | var user = new User({ 45 | username: req.body.username, 46 | password: req.body.password, 47 | nickname: req.body.nickname, 48 | avatar: "/images/default_avatar.jpg", 49 | owner: false, 50 | tags: [] 51 | }); 52 | user.save(function(err) { 53 | if (err) { 54 | return res.render("regist", { 55 | message: "发生错误,请稍后重试..." 56 | }); 57 | } else { 58 | req.session.user = user; 59 | res.redirect("/index"); 60 | } 61 | }); 62 | ``` 63 | ## update 64 | ### 说明 65 | > 更新当前用户 66 | 67 | ### 参数 68 | > **callback** [function] : 更新完成后的回调函数,一个参数 69 | 70 | >> **err** [object]: 若未发生错误,为null 71 | 72 | ### 实例 73 | ```js 74 | user.update(function(err) { 75 | req.session.user = user; 76 | res.render("userDetail", { 77 | user: req.session.user, 78 | success: true, 79 | message: "修改成功..." 80 | }); 81 | }); 82 | ``` 83 | ## remove 84 | ### 说明 85 | > 删除当前用户 86 | 87 | ### 参数 88 | > **callback** [function] : 删除完成后的回调函数,一个参数 89 | 90 | >> **err** [object]: 若未发生错误,为null 91 | 92 | 93 | ### 实例 94 | ```js 95 | user.remove(function(err) { 96 | if(err){ 97 | return res.render("error", { 98 | message: err.message 99 | }); 100 | } 101 | //do something after removing 102 | }); 103 | ``` 104 | # 方法 105 | *** 106 | ## get 107 | ### 说明 108 | > 根据用户的用户名,获取用户详细信息 109 | 110 | ### 参数 111 | > **username** [string] : 需要获取的用户的id 112 | 113 | > **callback** [function] : 获取完成后的回调函数,两个参数 114 | 115 | >> **err** [object]: 若未发生错误,为null 116 | 117 | >> **user** [User] : 该用户对象,没有则为null 118 | 119 | 120 | ### 实例 121 | ```js 122 | User.get(req.body.username, function(err, user) { 123 | if (err) return res.render("error", { 124 | message: err.message 125 | }); 126 | res.json({ 127 | username: user.username, 128 | nickname: user.nickname, 129 | owner: user.owner, 130 | tags: user.tags, 131 | avatar: user.avatar 132 | }); 133 | }); 134 | ``` 135 | 136 | ## countAll 137 | ### 说明 138 | > 统计系统中所有用户个数 139 | 140 | ### 参数 141 | > **callback** [function] : 删除完成后的回调函数,两个参数 142 | 143 | >> **err** [object]: 若未发生错误,为null 144 | 145 | >> **total** [number] : 系统中的用户总数 146 | 147 | 148 | ### 实例 149 | ```js 150 | User.count(function(err, total) { 151 | if(err) return res.json(500); 152 | return res.json({ 153 | total: total 154 | }); 155 | }); 156 | ``` 157 | ## getAll 158 | ### 说明 159 | > 分页获取系统中所有用户 160 | 161 | ### 参数 162 | > **curPage** [integer] : 当前页,从0开始 163 | 164 | > **perPage** [integer] : 每页用户个数 165 | 166 | > **callback** [function] : 删除完成后的回调函数,两个参数 167 | 168 | >> **err** [object]: 若未发生错误,为null 169 | 170 | >> **users** [array:User] : 获取的用户对象数组 171 | 172 | 173 | ### 实例 174 | ```js 175 | User.getAll(1, 10, function(err, useres) { 176 | if(err) return res.render("error"); 177 | return res.render("showUsers",{ 178 | useres: useres 179 | }); 180 | }); 181 | ``` 182 | -------------------------------------------------------------------------------- /routes/Model/Article.js: -------------------------------------------------------------------------------- 1 | var commonDao = require("./CommonDAO.js"), 2 | collectionName = "article", 3 | uuid = require("node-uuid"), 4 | async = require("async"), 5 | Bookmark = require("./Bookmark.js"), 6 | Comment = require("./Comment.js"), 7 | __resultToListFn = function(callback) { 8 | return function(err, results) { 9 | var i; 10 | if (err) return callback(err); 11 | for (i = results.length; i--;) { 12 | results[i] = new Article(results[i]); 13 | } 14 | callback(err, results); 15 | }; 16 | }; 17 | 18 | function Article(article) { 19 | this.content = article.content; 20 | this.title = article.title; 21 | this.writer = article.writer; 22 | this.id = article.id; 23 | this.writeTime = article.writeTime; 24 | this.lastModifyTime = article.lastModifyTime; 25 | this.tags = article.tags; 26 | } 27 | 28 | module.exports = Article; 29 | 30 | Article.prototype.save = function(callback) { 31 | var article = { 32 | content: this.content, 33 | title: this.title, 34 | writer: this.writer, 35 | writeTime: new Date().getTime(), 36 | lastModifyTime: new Date().getTime(), 37 | tags: this.tags, 38 | id: uuid.v4() 39 | }; 40 | commonDao.save(collectionName, article, function(err, result) { 41 | if (err) return callback(err); 42 | if (!result[0]) return callback(new Error("保存失败")); 43 | callback(err, new Article(result[0])); 44 | }); 45 | }; 46 | 47 | Article.prototype.update = function(callback) { 48 | commonDao.update(collectionName, { 49 | id: this.id 50 | }, { 51 | content: this.content, 52 | title: this.title, 53 | lastModifyTime: new Date().getTime(), 54 | tags: this.tags 55 | }, callback); 56 | }; 57 | 58 | Article.prototype.remove = function(callback) { 59 | var articleId = this.id; 60 | commonDao.remove(collectionName, { 61 | id: articleId 62 | }, callback); 63 | }; 64 | 65 | Article.get = function(id, callback) { 66 | commonDao.findOne(collectionName, { 67 | id: id 68 | }, function(err, result) { 69 | if (err) return callback(err); 70 | callback(err, result ? new Article(result) : result); 71 | }); 72 | }; 73 | 74 | Article.countAll = function(callback) { 75 | commonDao.count(collectionName, {}, callback); 76 | }; 77 | 78 | Article.getAll = function(curPage, perPage, callback) { 79 | commonDao.find(collectionName, { 80 | sort: { 81 | writeTime: -1 82 | }, 83 | page: { 84 | curPage: curPage, 85 | perPage: perPage 86 | } 87 | }, __resultToListFn(callback)); 88 | }; 89 | 90 | Article.getByUser = function(username, curPage, perPage, callback) { 91 | commonDao.find(collectionName, { 92 | condition: { 93 | writer: username 94 | }, 95 | sort: { 96 | writeTime: -1 97 | }, 98 | page: { 99 | curPage: curPage, 100 | perPage: perPage 101 | } 102 | }, __resultToListFn(callback)); 103 | }; 104 | 105 | Article.countByUser = function(username, callback) { 106 | commonDao.count(collectionName, { 107 | writer: username 108 | }, callback); 109 | }; 110 | 111 | Article.getByTags = function(tags, curPage, perPage, callback) { 112 | commonDao.find(collectionName, { 113 | condition: { 114 | tags: { 115 | $all: tags 116 | } 117 | }, 118 | sort: { 119 | writeTime: -1 120 | }, 121 | page: { 122 | curPage: curPage, 123 | perPage: perPage 124 | } 125 | }, __resultToListFn(callback)); 126 | }; 127 | 128 | Article.getByTitle = function(title, curPage, perPage, callback) { 129 | console.log(title); 130 | commonDao.find(collectionName, { 131 | condition: { 132 | title: new RegExp(title, "i") 133 | }, 134 | sort: { 135 | writeTime: -1 136 | }, 137 | page: { 138 | curPage: curPage, 139 | perPage: perPage 140 | } 141 | }, __resultToListFn(callback)); 142 | }; -------------------------------------------------------------------------------- /views/layout.jade: -------------------------------------------------------------------------------- 1 | doctype 5 2 | html(xmlns:wb="http://open.weibo.com/wb") 3 | head 4 | meta(property="wb:webmaster", content="8984d0c69a7d9445") 5 | meta(name="description", content="天镶的博客,使用nodejs+mongodb搭建") 6 | meta(name="keywords", content="天镶,博客,skyinlayer,nodejs,mongodb,前端,html,css,javascript") 7 | title 天镶的博客 8 | link(rel="shortcut icon", href="/favicon.ico", type="image/x-icon") 9 | link(rel='stylesheet', href='/stylesheets/bootstrap.min.css') 10 | link(rel='stylesheet', href='/stylesheets/pageTools/remind.css') 11 | link(rel='stylesheet', href='/stylesheets/style.css') 12 | block style 13 | 14 | body 15 | div(class="b-clouds") 16 | div(class="clouds") 17 | div(class="cloud cloud-lg", style="left: 10%;top: 20%") 18 | div(class="cloud cloud-sm", style="top: 30%") 19 | div(class="cloud cloud-md", style="left: 20%;top: 50%") 20 | div(class="cloud cloud-lg", style="left: 60%;top: 20%") 21 | div(class="cloud cloud-sm", style="left: 50%;top: 30%") 22 | div(class="cloud cloud-md", style="left: 70%;top: 40%") 23 | div(class="block") 24 | div(class="scrollToTop", style="display:none", id="scrollToTop") 25 | span(class="glyphicon glyphicon-arrow-up") 26 | div(class='navbar navbar-inverse navbar-fixed-top') 27 | div(class="container") 28 | div(class='navbar-header') 29 | button(type='button', class='navbar-toggle', data-toggle='collapse', data-target='.navbar-collapse') 30 | span(class='icon-bar') 31 | span(class='icon-bar') 32 | span(class='icon-bar') 33 | a(class='navbar-brand', href='/index') 天镶的博客 34 | 35 | div(class='collapse navbar-collapse') 36 | ul(class='nav navbar-nav') 37 | li 38 | a(href='/index') 首页 39 | li 40 | a(href='/articleList') 文章列表 41 | li 42 | a(href='/search') 搜索 43 | li 44 | a(href='/gallary') 图片墙 45 | li(class="dropdown") 46 | a(href="#", class="dropdown-toggle", data-toggle="dropdown") 小玩意 47 | b(class="caret") 48 | ul(class="dropdown-menu") 49 | li 50 | a(href="/broadchat") 版聊 51 | li 52 | a(href="/advicePage") 建议 53 | if user 54 | li 55 | a(href="/userCenter") 个人中心 56 | ul(class='nav navbar-nav navbar-right') 57 | if user 58 | li(class="dropdown") 59 | a(href="#", class="dropdown-toggle", data-toggle="dropdown", id="hasRemind") 60 | li(class="dropdown") 61 | a(href="#", class="dropdown-toggle", data-toggle="dropdown") #{user.nickname} 62 | b(class="caret") 63 | ul(class="dropdown-menu") 64 | if user.owner 65 | li 66 | a(href="/nor/conf/article_write") 写新文章 67 | li 68 | a(href="/article_list") 管理文章 69 | li 70 | a(href="/nor/user_detail") 修改个人信息 71 | li 72 | a(href="/user_logout") 登出 73 | else 74 | li 75 | a(href='/user_loginPage') 登录 76 | div(class="g-main") 77 | block content 78 | div(class="footer") 79 | hr 80 | p LingyuCoder 2013 81 | 82 | script(type='text/javascript', src='/javascripts/tools/jquery-1.10.2.min.js') 83 | script(type='text/javascript', src='/javascripts/tools/bootstrap.min.js') 84 | script(type='text/javascript', src='/javascripts/pageTools/Remind.js') 85 | script(type='text/javascript', src='/javascripts/page/global.js') 86 | 87 | block javascript 88 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ################# 2 | ## Eclipse 3 | ################# 4 | 5 | *.pydevproject 6 | .project 7 | .metadata 8 | bin/ 9 | tmp/ 10 | *.tmp 11 | *.bak 12 | *.swp 13 | *~.nib 14 | local.properties 15 | .classpath 16 | .settings/ 17 | .loadpath 18 | 19 | # External tool builders 20 | .externalToolBuilders/ 21 | 22 | # Locally stored "Eclipse launch configurations" 23 | *.launch 24 | 25 | # CDT-specific 26 | .cproject 27 | 28 | # PDT-specific 29 | .buildpath 30 | 31 | 32 | ################# 33 | ## Visual Studio 34 | ################# 35 | 36 | ## Ignore Visual Studio temporary files, build results, and 37 | ## files generated by popular Visual Studio add-ons. 38 | 39 | # User-specific files 40 | *.suo 41 | *.user 42 | *.sln.docstates 43 | 44 | # Build results 45 | 46 | [Dd]ebug/ 47 | [Rr]elease/ 48 | x64/ 49 | build/ 50 | [Bb]in/ 51 | [Oo]bj/ 52 | 53 | # MSTest test Results 54 | [Tt]est[Rr]esult*/ 55 | [Bb]uild[Ll]og.* 56 | 57 | *_i.c 58 | *_p.c 59 | *.ilk 60 | *.meta 61 | *.obj 62 | *.pch 63 | *.pdb 64 | *.pgc 65 | *.pgd 66 | *.rsp 67 | *.sbr 68 | *.tlb 69 | *.tli 70 | *.tlh 71 | *.tmp 72 | *.tmp_proj 73 | *.log 74 | *.vspscc 75 | *.vssscc 76 | .builds 77 | *.pidb 78 | *.log 79 | *.scc 80 | 81 | # Visual C++ cache files 82 | ipch/ 83 | *.aps 84 | *.ncb 85 | *.opensdf 86 | *.sdf 87 | *.cachefile 88 | 89 | # Visual Studio profiler 90 | *.psess 91 | *.vsp 92 | *.vspx 93 | 94 | # Guidance Automation Toolkit 95 | *.gpState 96 | 97 | # ReSharper is a .NET coding add-in 98 | _ReSharper*/ 99 | *.[Rr]e[Ss]harper 100 | 101 | # TeamCity is a build add-in 102 | _TeamCity* 103 | 104 | # DotCover is a Code Coverage Tool 105 | *.dotCover 106 | 107 | # NCrunch 108 | *.ncrunch* 109 | .*crunch*.local.xml 110 | 111 | # Installshield output folder 112 | [Ee]xpress/ 113 | 114 | # DocProject is a documentation generator add-in 115 | DocProject/buildhelp/ 116 | DocProject/Help/*.HxT 117 | DocProject/Help/*.HxC 118 | DocProject/Help/*.hhc 119 | DocProject/Help/*.hhk 120 | DocProject/Help/*.hhp 121 | DocProject/Help/Html2 122 | DocProject/Help/html 123 | 124 | # Click-Once directory 125 | publish/ 126 | 127 | # Publish Web Output 128 | *.Publish.xml 129 | *.pubxml 130 | 131 | # NuGet Packages Directory 132 | ## TODO: If you have NuGet Package Restore enabled, uncomment the next line 133 | #packages/ 134 | 135 | # Windows Azure Build Output 136 | csx 137 | *.build.csdef 138 | 139 | # Windows Store app package directory 140 | AppPackages/ 141 | 142 | # Others 143 | sql/ 144 | *.Cache 145 | ClientBin/ 146 | [Ss]tyle[Cc]op.* 147 | ~$* 148 | *~ 149 | *.dbmdl 150 | *.[Pp]ublish.xml 151 | *.pfx 152 | *.publishsettings 153 | 154 | # RIA/Silverlight projects 155 | Generated_Code/ 156 | 157 | # Backup & report files from converting an old project file to a newer 158 | # Visual Studio version. Backup files are not needed, because we have git ;-) 159 | _UpgradeReport_Files/ 160 | Backup*/ 161 | UpgradeLog*.XML 162 | UpgradeLog*.htm 163 | 164 | # SQL Server files 165 | App_Data/*.mdf 166 | App_Data/*.ldf 167 | 168 | ############# 169 | ## Windows detritus 170 | ############# 171 | 172 | # Windows image file caches 173 | Thumbs.db 174 | ehthumbs.db 175 | 176 | # Folder config file 177 | Desktop.ini 178 | 179 | # Recycle Bin used on file shares 180 | $RECYCLE.BIN/ 181 | 182 | # Mac crap 183 | .DS_Store 184 | 185 | 186 | ############# 187 | ## Python 188 | ############# 189 | 190 | *.py[co] 191 | 192 | # Packages 193 | *.egg 194 | *.egg-info 195 | dist/ 196 | build/ 197 | eggs/ 198 | parts/ 199 | var/ 200 | sdist/ 201 | develop-eggs/ 202 | .installed.cfg 203 | 204 | # Installer logs 205 | pip-log.txt 206 | 207 | # Unit test / coverage reports 208 | .coverage 209 | .tox 210 | 211 | #Translations 212 | *.mo 213 | 214 | #Mr Developer 215 | .mr.developer.cfg 216 | -------------------------------------------------------------------------------- /public/javascripts/tools/showdown_extensions/table.js: -------------------------------------------------------------------------------- 1 | /*global module:true*/ 2 | /* 3 | * Basic table support with re-entrant parsing, where cell content 4 | * can also specify markdown. 5 | * 6 | * Tables 7 | * ====== 8 | * 9 | * | Col 1 | Col 2 | 10 | * |======== |====================================================| 11 | * |**bold** | ![Valid XHTML] (http://w3.org/Icons/valid-xhtml10) | 12 | * | Plain | Value | 13 | * 14 | */ 15 | 16 | (function(){ 17 | var table = function(converter) { 18 | var tables = {}, style = 'text-align:left;', filter; 19 | tables.th = function(header){ 20 | if (header.trim() === "") { return "";} 21 | var id = header.trim().replace(/ /g, '_').toLowerCase(); 22 | return '' + header + ''; 23 | }; 24 | tables.td = function(cell) { 25 | return '' + converter.makeHtml(cell) + ''; 26 | }; 27 | tables.ths = function(){ 28 | var out = "", i = 0, hs = [].slice.apply(arguments); 29 | for (i;i'); 65 | hs = line.substring(1, line.length -1).split('|'); 66 | tbl.push(tables.thead.apply(this, hs)); 67 | line = lines[++i]; 68 | if (!line.trim().match(/^[|]{1}[-=| ]+[|]{1}$/)) { 69 | // not a table rolling back 70 | line = lines[--i]; 71 | } 72 | else { 73 | line = lines[++i]; 74 | tbl.push(''); 75 | while (line.trim().match(/^[|]{1}.*[|]{1}$/)) { 76 | line = line.trim(); 77 | tbl.push(tables.tr.apply(this, line.substring(1, line.length -1).split('|'))); 78 | line = lines[++i]; 79 | } 80 | tbl.push(''); 81 | tbl.push(''); 82 | // we are done with this table and we move along 83 | out.push(tbl.join('\n')); 84 | continue; 85 | } 86 | } 87 | out.push(line); 88 | } 89 | return out.join('\n'); 90 | }; 91 | return [ 92 | { 93 | type: 'lang', 94 | filter: filter 95 | } 96 | ]; 97 | }; 98 | 99 | // Client-side export 100 | if (typeof window !== 'undefined' && window.Showdown && window.Showdown.extensions) { window.Showdown.extensions.table = table; } 101 | // Server-side export 102 | if (typeof module !== 'undefined') { 103 | module.exports = table; 104 | } 105 | }()); 106 | -------------------------------------------------------------------------------- /views/userDetail.jade: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | block style 4 | link(rel='stylesheet', href='/stylesheets/pageTools/tag.css') 5 | link(rel='stylesheet', href='/stylesheets/pageTools/user.css') 6 | 7 | block content 8 | div(class='container') 9 | h1 10 | strong 个人信息 11 | form(class="form-horizontal", method="post", action="/nor/user_modify", id="detail") 12 | div(class="u-panel") 13 | if message 14 | if success 15 | div(class="alert alert-success") #{message} 16 | else 17 | div(class="alert alert-danger") #{message} 18 | div(class="alert alert-danger", id="formValidate", style="display:none") 19 | div(class="form-group") 20 | label(for="inputUsername", class="col-sm-2 col-sm-offset-2 control-label") 用户名 21 | div(class="col-sm-4") 22 | input(type="text", name="username", readonly="readonly", class="form-control", id="inputUsername", value="#{user.username}") 23 | div(class="form-group") 24 | label(for="inputNickname", class="col-sm-2 col-sm-offset-2 control-label") 昵称 25 | div(class="col-sm-4") 26 | input(type="text", name="nickname", class="form-control", id="inputNickname", value="#{user.nickname}") 27 | div(class="form-group") 28 | label(for="inputAvatar", class="col-sm-2 col-sm-offset-2 control-label") 头像地址 29 | div(class="col-sm-4") 30 | input(type="text", name="avatar", class="form-control", id="inputAvatar", value="#{user.avatar}") 31 | div(class="form-group") 32 | label(class="col-sm-2 col-sm-offset-2 control-label") 头像预览 33 | div(class="col-sm-4") 34 | div(class="u-user-avatar") 35 | img(src="#{user.avatar}",id="avatarPreview") 36 | div(class="form-group") 37 | label(for="inputPassword", class="col-sm-2 col-sm-offset-2 control-label") 密码 38 | div(class="col-sm-4") 39 | input(type="password", name="password", class="form-control", id="inputPassword", placeholder="填写密码,为空则不修改") 40 | div(class="form-group") 41 | label(for="inputPassAgain", class="col-sm-2 col-sm-offset-2 control-label") 密码重复 42 | div(class="col-sm-4") 43 | input(type="password", name="passwordAgain", class="form-control", id="inputPassAgain", placeholder="再次填写密码") 44 | div(class="form-group", style="margin-top:20px") 45 | input(type="hidden", name="tags") 46 | div(class="col-sm-6") 47 | div(class="u-panel") 48 | h3 您的标签 49 | hr 50 | div(id="myTags", class="m-myTags") 51 | div(class="col-sm-6") 52 | div(class="u-panel") 53 | h3 所有标签 54 | hr 55 | div(id="allTags", class="m-allTags") 56 | hr 57 | span(class="label b-label-1", style="padding:8px 5px", id="newTag") 58 | input(id="newTagInput", type="text", placeholder="标签名", color="#428bca") 59 | span(class="glyphicon glyphicon-plus u-tag-create", style="margin-left:5px;cursor:pointer", id="createTag") 60 | span(class="label b-label-1 u-label-picker", value=1, color="#428bca") 深蓝 61 | span(class="label b-label-2 u-label-picker", value=2, color="#5cb85c") 绿色 62 | span(class="label b-label-3 u-label-picker", value=3, color="#5cb0de") 淡蓝 63 | span(class="label b-label-4 u-label-picker", value=4, color="#f0ad4e") 黄色 64 | span(class="label b-label-5 u-label-picker", value=5, color="#d9543f") 红色 65 | div(class="form-group") 66 | button(type="submit" class="col-sm-offset-3 col-sm-2 btn btn-primary") 提交修改 67 | a(class="btn btn-default col-sm-offset-2 col-sm-2", href="user_detail") 恢复 68 | 69 | block javascript 70 | script(type='text/javascript', src='/javascripts/pageTools/Tag.js') 71 | script(type='text/javascript', src='/javascripts/page/userDetail.js') 72 | 73 | -------------------------------------------------------------------------------- /doc/commonDAO.md: -------------------------------------------------------------------------------- 1 | 公用数据库连接层 [CommonDAO.js] 2 | === 3 | *** 4 | 使用[node-mongodb-native](https://github.com/mongodb/node-mongodb-native)直接与mongodb数据库相连 5 | 6 | 提供以下方法: 7 | 8 | 1. [save](#save) 9 | 2. [findOne](#findone) 10 | 3. [find](#find) 11 | 4. [count](#count) 12 | 5. [update](#update) 13 | 6. [remove](#remove) 14 | 15 | ## save 16 | *** 17 | ### 说明 18 | > 用于将一个对象保存到mongodb数据库中 19 | 20 | ### 参数 21 | > **collectionName** [string]:mongodb的collection名称 22 | 23 | > **obj** [object] : 需要保存到collection中的对象 24 | 25 | > **callback** [function]:保存完毕后执行的回调函数 26 | 27 | >> **err** [object]: 若未发生错误,为null 28 | 29 | >> **result** [array:object] :result[0]为存入数据库的对象,若长度为0则保存失败 30 | 31 | ### 实例 32 | ```js 33 | commonDao.save("article", { 34 | id: uuid.v4, 35 | content: "hello world" 36 | }, function(err, result) { 37 | if (err) return callback(err); 38 | if (!result[0]) return callback(new Error("保存失败")); 39 | callback(err, new Article(result[0])); 40 | }); 41 | ``` 42 | 43 | ## findOne 44 | *** 45 | ### 说明 46 | > 根据特定条件在数据库中查找一个匹配的对象 47 | 48 | ### 参数 49 | > **collectionName** [string]: mongodb的collection名称 50 | 51 | > **oArgs** [object]: mongodb查询时的条件 52 | 53 | > **callback** [function]: 查询执行完后的回调函数,两个参数: 54 | 55 | >> **err** [object]: 若未发生错误,则为null 56 | 57 | >> **result** [object]: 查询结果 58 | 59 | ### 实例 60 | 根据文章id查找文章 61 | ```js 62 | commonDao.findOne("article", { 63 | id: id 64 | }, callback); 65 | ``` 66 | 67 | ## find 68 | *** 69 | ### 说明 70 | > 根据特定条件和配置在数据库中查找所有匹配的对象 71 | 72 | ### 参数 73 | > **collectionName** [string] : mongodb的collection名称 74 | 75 | > **oArgs** [object] : mongodb查询的条件及分页、排序设置 76 | 77 | >> **condition** [object] : mongodb查询的条件 78 | 79 | >> **page** [object] : 查询的分页设置 80 | > 81 | >>> **curPage** [integer] : 查询的页数,从0开始 82 | > 83 | >>> **perPage** [integer] : 查询每页的个数 84 | > 85 | >> **sort** [object] : 查询的排序方式,键为排序的字段名称,值为**1**表示**正序**,值为**-1**表示倒序 86 | > 87 | > **callback** [function] : 查询完成之后的回调函数,有两个参数: 88 | > 89 | >> **err** [object]: 若未发生错误,则为null 90 | > 91 | >> **results** [array:object]: 查询结果数组 92 | 93 | ### 实例 94 | 以创作时间为倒序,每页10个结果,查询第二页的所有文章 95 | ```js 96 | commonDao.find("article", { 97 | sort: { 98 | writeTime: -1 99 | }, 100 | page: { 101 | curPage: 1, 102 | perPage: 10 103 | } 104 | }, callback); 105 | ``` 106 | 107 | ## count 108 | *** 109 | ### 说明 110 | > 统计数据库中符合条件的对象个数 111 | 112 | ### 参数 113 | > **collectionName** [string] : mongodb的collection名称 114 | > 115 | > **oArgs** [object] : 统计的条件 116 | > 117 | > **callback** [function] : 统计完成后的回调函数,两个参数: 118 | > > **err** [object] : 若未发生错误,则为null 119 | > 120 | > > **total** [number] : 统计的结果 121 | 122 | ### 实例 123 | 通过作者用户名查询文章 124 | ```js 125 | commonDao.count("article", { 126 | writer: username 127 | }, callback); 128 | ``` 129 | ## update 130 | *** 131 | ### 说明 132 | > 更新所有符合条件的对象 133 | 134 | ### 参数 135 | > **collectionName** [string] : mongodb的collection名称 136 | 137 | > **oArgs** [object] : 查找需要被更新对象的条件 138 | 139 | > **change** [object] : 需要被更新的数据,键为更新的字段名称,值为需要被更新的值 140 | 141 | > **callback** [function] : 更新完成后的回调函数,一个参数: 142 | 143 | >> **err** [object] : 若未发生错误,则为null 144 | 145 | ### 实例 146 | 更新一个文章的主题,内容,修改时间,及标签 147 | ```js 148 | commonDao.update("article", { 149 | id: this.id 150 | }, { 151 | content: this.content, 152 | title: this.title, 153 | lastModifyTime: new Date().getTime(), 154 | tags: this.tags 155 | }, callback); 156 | ``` 157 | ## remove 158 | *** 159 | ### 说明 160 | > 移除所有符合条件的对象 161 | 162 | ### 参数 163 | > **collectionName** [string] : mongodb的collection名称 164 | 165 | > **oArgs** [object] : 查找需要被删除对象的条件 166 | 167 | > **callback** [function] : 删除完成后的回调函数,一个参数: 168 | 169 | >> **err** [object] : 若未发生错误,则为null 170 | 171 | ### 实例 172 | 根据文章id删除一篇文章 173 | ```js 174 | commonDao.remove("article", { 175 | id: articleId 176 | }, callback); 177 | ``` -------------------------------------------------------------------------------- /routes/Actions/CommentAction.js: -------------------------------------------------------------------------------- 1 | var Comment = require("../Model/Comment.js"), 2 | Remind = require("../Model/Remind.js"), 3 | moment = require("moment"), 4 | async = require("async"); 5 | 6 | moment.lang("zh-cn"); 7 | 8 | exports.save = function(req, res) { 9 | var remind, 10 | comment = new Comment({ 11 | articleId: req.body.articleId, 12 | username: req.session.user.username, 13 | comment: req.body.comment, 14 | reply: req.body.replyId 15 | }); 16 | 17 | async.waterfall([ 18 | 19 | function(callback) { 20 | comment.save(function(err, comment) { 21 | if (err) return callback(err); 22 | callback(err, comment); 23 | }); 24 | }, 25 | function(comment, callback) { 26 | if (comment.reply) { 27 | Comment.get(comment.reply, function(err, com) { 28 | if (err) return callback(err); 29 | var remind = new Remind({ 30 | type: "comment", 31 | ref: comment.id, 32 | user: com.username 33 | }); 34 | remind.save(function(err) { 35 | if (err) return callback(err); 36 | callback(comment); 37 | }); 38 | }); 39 | } else { 40 | callback(comment); 41 | } 42 | }, 43 | function(comment, callback) { 44 | Article.get(comment.articleId, function(err, com) { 45 | if (err) return callback(err); 46 | var remind = new Remind({ 47 | type: "comment", 48 | ref: comment.id, 49 | user: com.username 50 | }); 51 | remind.save(function(err) { 52 | if (err) return callback(err); 53 | callback(comment); 54 | }); 55 | }); 56 | } 57 | ], function(err) { 58 | if (err) res.render("error", { 59 | message: err.message 60 | }); 61 | res.redirect("/article_load?articleId=" + comment.articleId + "#comments"); 62 | }); 63 | }; 64 | 65 | exports.remove = function(req, res) { 66 | Comment.get(req.body.commentId, function(err, comment) { 67 | if (err) return res.json(500, { 68 | message: err.message 69 | }); 70 | if (req.session.user.username === comment.username) { 71 | comment.remove(function(err) { 72 | if (err) return res.json(500, { 73 | message: err.message 74 | }); 75 | res.json({ 76 | success: true 77 | }); 78 | }); 79 | } else { 80 | return res.json(500, { 81 | message: "不能删除他人的评论" 82 | }); 83 | } 84 | }); 85 | }; 86 | 87 | exports.getByArticle = function(req, res) { 88 | Comment.getByArticle(req.body.articleId, Number(req.body.curPage), Number(req.body.perPage), function(err, comments) { 89 | var i; 90 | if (err) return res.json(500, { 91 | message: err.message 92 | }); 93 | for (i = comments.length; i--;) { 94 | comments[i].time = moment(comments[i].time).fromNow(); 95 | } 96 | res.json({ 97 | comments: comments 98 | }); 99 | }); 100 | }; 101 | 102 | exports.countByArticle = function(req, res) { 103 | Comment.countByArticle(req.body.articleId, function(err, total) { 104 | if (err) return res.json(500, { 105 | message: err.message 106 | }); 107 | res.json({ 108 | total: total 109 | }); 110 | }); 111 | }; 112 | 113 | exports.countByUser = function(req, res) { 114 | Comment.countByUser(req.body.username, function(err, total) { 115 | if (err) return res.json(500, { 116 | message: err.message 117 | }); 118 | res.json({ 119 | total: total 120 | }); 121 | }); 122 | }; 123 | 124 | exports.getByUser = function(req, res) { 125 | Comment.getByUser(req.body.username, Number(req.body.curPage), Number(req.body.perPage), function(err, comments) { 126 | if (err) return res.json(500, { 127 | message: err.message 128 | }); 129 | for (var i = comments.length; i--;) { 130 | comments[i].time = moment(comments[i].time).fromNow(); 131 | } 132 | res.json({ 133 | comments: comments 134 | }); 135 | }); 136 | }; 137 | 138 | 139 | exports.getOne = function(req, res) { 140 | Comment.get(req.body.commentId, function(err, comment) { 141 | if (err) return res.json(500); 142 | if (!comment) return res.status(404).send("not fount"); 143 | comment.time = moment(comment.time).fromNow(); 144 | res.json({ 145 | comment: comment 146 | }); 147 | }); 148 | }; -------------------------------------------------------------------------------- /views/articleDetail.jade: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | block style 4 | link(rel='stylesheet', href='/stylesheets/github.css') 5 | link(rel='stylesheet', href='/stylesheets/pageTools/tag.css') 6 | link(rel='stylesheet', href='/stylesheets/pageTools/admire.css') 7 | link(rel='stylesheet', href='/stylesheets/pageTools/comment.css') 8 | link(rel='stylesheet', href='/stylesheets/pageTools/user.css') 9 | link(rel='stylesheet', href='/stylesheets/pageTools/bookmark.css') 10 | link(rel='stylesheet', href='/stylesheets/page/articleDetail.css') 11 | //- script(src="http://tjs.sjs.sinajs.cn/open/api/js/wb.js", type="text/javascript", charset="utf-8") 12 | block content 13 | div(class="container") 14 | div(class="g-art", id="artBody") 15 | div(class="u-panel g-art-body") 16 | div(class="u-flag") 17 | div(class="u-flag-left") 18 | h3 19 | !=article.title 20 | section 21 | !=article.content 22 | div(class="g-art-footer") 23 | div(id="bookmark", aid="#{article.id}", cur=user?"#{user.username}":"") 24 | div(style="float:right;padding:5px 10px") 25 | //- wb:share-button(appkey="1yj5nG", addition="number", type="button", default_text="#{article.title} 来自 天镶的博客", ralateUid="1162383197") 26 | div(class="clearfix") 27 | hr 28 | div(id="comments", aid="#{article.id}", cur="0", curUser=user?"#{user.username}":"") 29 | div(class="g-container") 30 | div(class="u-more") 点击获取更多 31 | 32 | div(id="replyComment", class="g-reply-form") 33 | form(action="/nor/comment_addComment", method="post") 34 | input(type="hidden", name="articleId", value="#{article.id}") 35 | input(type="hidden", name="replyId") 36 | div(class="form-group") 37 | textarea(class="form-control", name="comment", id="inputComment", style="resize: none", rows=2) 38 | div(class="form-group") 39 | button(class="btn btn-success", type="submit", style="float:right") 发送 40 | div(class="clearfix") 41 | 42 | div(id="newComments") 43 | if user 44 | h3 新评论: 45 | form(action="/nor/comment_addComment", method="post") 46 | input(type="hidden", name="articleId", value="#{article.id}") 47 | div(class="form-group") 48 | textarea(class="form-control", name="comment", id="inputComment", style="resize: none", rows=4) 49 | div(class="form-group") 50 | button(class="btn btn-success", id="sendComment", type="submit", style="float:left") 发送 51 | button(class="btn btn-danger", type="button" ,id="clearComment", style="float:right") 清空 52 | else 53 | h3(style="text-align:center") 请登录后评论 54 | div(class="g-art-info hidden-sm") 55 | div(class="u-panel") 56 | div(class="g-user") 57 | div(class="u-avatar", uid="#{article.writer}") 58 | div(class="u-nick") 59 | div(class="clearfix") 60 | div(class="u-time") #{"作于: " + article.writeTime} 61 | if user&&user.owner 62 | div(class="g-opt") 63 | a(class="btn btn-primary", style="width:100%;margin-top:10px", href="/nor/conf/article_edit?articleId=#{article.id}") 修改 64 | button(class="btn btn-danger", aid="#{article.id}",style="width:100%;margin-top:10px", id="deleteArticle") 删除 65 | div(class="u-panel", style="margin-top: 20px") 文章标签: 66 | div(class="u-tags", id="tags", aid="#{article.id}") 67 | 68 | block javascript 69 | script(type='text/javascript', src='/javascripts/tools/highlight.pack.js') 70 | script(type='text/javascript', src='/javascripts/pageTools/Tag.js') 71 | script(type='text/javascript', src='/javascripts/pageTools/Admire.js') 72 | script(type='text/javascript', src='/javascripts/pageTools/Comment.js') 73 | script(type='text/javascript', src='/javascripts/pageTools/Article.js') 74 | script(type='text/javascript', src='/javascripts/pageTools/User.js') 75 | script(type='text/javascript', src='/javascripts/pageTools/Bookmark.js') 76 | script(type='text/javascript', src='/javascripts/page/articleDetail.js') -------------------------------------------------------------------------------- /doc/bookmark.md: -------------------------------------------------------------------------------- 1 | 书签 [Bookmark] 2 | === 3 | *** 4 | #构造函数 5 | ```js 6 | function Bookmark(bookmark) { 7 | this.username = bookmark.username; 8 | this.articleId = bookmark.articleId; 9 | this.time = bookmark.time; 10 | this.id = bookmark.id; 11 | } 12 | ``` 13 | #属性 14 | > **id** [string] : uuid v4生成的字符串 15 | 16 | > **username** [string] : 书签拥有者的用户名 17 | 18 | > **articleId** [string] : 书签所属文章的id 19 | 20 | > **time** [number] : 创建书签的时间,为毫秒数 21 | 22 | #原型上的方法 23 | *** 24 | ##save 25 | ###说明 26 | > 保存当前书签对象 27 | 28 | ###参数 29 | > **callback** [function] : 保存完成后的回调函数,两个参数 30 | 31 | >> **err** [object]: 若未发生错误,为null 32 | 33 | >> **result** [Bookmark] :持久化后的Bookmark对象 34 | 35 | ###实例 36 | ```js 37 | var bookmark = new Bookmark({ 38 | articleId: req.body.articleId, 39 | username: req.session.user.username 40 | }); 41 | bookmark.save(function(err, bookmark) { 42 | //TODO 43 | }); 44 | ``` 45 | 46 | ##remove 47 | ###说明 48 | > 移除当前书签对象 49 | 50 | ###参数 51 | > **callback** [function] : 移除完成后的回调函数,一个参数 52 | 53 | >> **err** [object]: 若未发生错误,为null 54 | 55 | ###实例 56 | ```js 57 | bookmark.remove(function(err) { 58 | if (err) return res.json(500, { 59 | message: err.message 60 | }); 61 | res.json({ 62 | success: true 63 | }); 64 | }); 65 | ``` 66 | 67 | #方法 68 | *** 69 | ##get 70 | ###说明 71 | > 根据书签id获取一个书签 72 | 73 | ###参数 74 | > **bookmarkId** [string] : 书签的id 75 | 76 | > **callback** [function] : 移除完成后的回调函数,两个参数 77 | 78 | >> **err** [object]: 若未发生错误,为null 79 | 80 | >> **bookmark** [Bookmark] : 获取到的书签对象,若失败,则为null 81 | 82 | ###实例 83 | ```js 84 | Bookmark.get(req.body.bookmarkId, function(err, bookmark) { 85 | if (err) return res.json(500); 86 | if (!bookmark) return res.status(404).send("not found"); 87 | bookmark.time = moment(bookmark.time).format("HH:mm MM月DD日 YYYY年"); 88 | res.json({ 89 | bookmark: bookmark 90 | }); 91 | }); 92 | ``` 93 | ##checkBooked 94 | ###说明 95 | > 判断一个用户是否收藏了某篇文章 96 | 97 | ###参数 98 | > **username** [string] : 用户的用户名 99 | 100 | > **articleId** [string] : 文章的id 101 | 102 | > **callback** [function] : 移除完成后的回调函数,两个参数 103 | 104 | >> **err** [object]: 若未发生错误,为null 105 | 106 | >> **booked** [boolean] : 若已经加入收藏,则为true,否则为false 107 | 108 | ###实例 109 | ```js 110 | Bookmark.checkBooked(req.session.user.username, req.body.articleId, function(err, booked) { 111 | if (err) return res.json(500, { 112 | message: err.message 113 | }); 114 | res.json({ 115 | booked: booked 116 | }); 117 | }); 118 | ``` 119 | 120 | ##countByArticle 121 | ###说明 122 | > 统计一篇文章的被收藏次数 123 | 124 | ###参数 125 | > **articleId** [string] : 文章的id 126 | 127 | > **callback** [function] : 移除完成后的回调函数,两个参数 128 | 129 | >> **err** [object]: 若未发生错误,为null 130 | 131 | >> **total** [number] : 文章被收藏次数 132 | 133 | ###实例 134 | ```js 135 | Bookmark.countByArticle(req.body.articleId, function(err, total) { 136 | if (err) return res.json(500, { 137 | message: err.message 138 | }); 139 | res.json({ 140 | total: total 141 | }); 142 | }); 143 | ``` 144 | 145 | ##countByUser 146 | ###说明 147 | > 统计一个用户的书签总个数 148 | 149 | ###参数 150 | > **username** [string] :用户的用户名 151 | 152 | > **callback** [function] : 移除完成后的回调函数,两个参数 153 | 154 | >> **err** [object]: 若未发生错误,为null 155 | 156 | >> **total** [number] : 用户的书签总个数 157 | 158 | ###实例 159 | ```js 160 | Bookmark.countByUser(req.body.username, function(err, total) { 161 | if (err) return res.json(500); 162 | res.json({ 163 | total: total 164 | }); 165 | }); 166 | ``` 167 | ##getByUser 168 | ###说明 169 | > 分页获取一个用户的所有书签 170 | 171 | ###参数 172 | > **username** [string] :用户的用户名 173 | 174 | > **curPage** [integer] : 当前页,从0开始 175 | 176 | > **perPage** [integer] : 每页的书签个数 177 | 178 | > **callback** [function] : 查询完成后的回调函数 179 | 180 | >> **err** [object]: 若未发生错误,为null 181 | 182 | >> **bookmarks** [array:Bookmark] : 获取到的书签对象的数组 183 | 184 | ###实例 185 | ```js 186 | Bookmark.getByUser(req.body.username, Number(req.body.curPage), Number(req.body.perPage), function(err, bookmarks) { 187 | if (err) return res.json(500); 188 | for (var i = bookmarks.length; i--;) { 189 | bookmarks[i].time = moment(bookmarks[i].time).format("HH:mm MM月DD日 YYYY年"); 190 | } 191 | res.json({ 192 | bookmarks: bookmarks 193 | }); 194 | }); 195 | ``` 196 | -------------------------------------------------------------------------------- /public/javascripts/page/articleDetail.js: -------------------------------------------------------------------------------- 1 | (function($, window) { 2 | var $replyComment = $("#replyComment"), 3 | $comments = $("#comments"), 4 | $bookmark = $("#bookmark"), 5 | curUser = $comments.attr("curUser"), 6 | __readMoreComments = function(event) { 7 | var articleId = $comments.attr("aid"), 8 | curPage = Number($comments.attr("cur")), 9 | perPage = 10, 10 | commentContainer = $comments.find(".g-container"); 11 | $comments.find(".u-more").text("正在努力加载中...").unbind("click", __readMoreComments); 12 | $(document).trigger("comment.getByArticle", [articleId, curPage, perPage, 13 | function(err, comments) { 14 | var i, m, container, 15 | __deleteFn = function(container) { 16 | return function(event) { 17 | var comment = $(this).data("comment"); 18 | $(document).trigger("comment.remove", [comment.id, 19 | function(err) { 20 | if (err) return; 21 | container.fadeOut(function() { 22 | container.remove(); 23 | }); 24 | } 25 | ]); 26 | }; 27 | }, 28 | __replyFn = function(container) { 29 | return function(event) { 30 | var comment = $(this).data("comment"); 31 | $("textarea", $replyComment).val(""); 32 | if ($("form input[name='replyId']", $replyComment).val() !== comment.id) { 33 | $replyComment.slideUp(function() { 34 | container.find(".g-comment-row:first").append($replyComment); 35 | $replyComment.slideDown(function() { 36 | $("textarea", $replyComment).focus(); 37 | }); 38 | $("form input[name='replyId']").val(comment.id); 39 | }); 40 | } else { 41 | if ($replyComment.css("display") === "none") { 42 | $replyComment.slideDown(function() { 43 | $("textarea", $replyComment).focus(); 44 | }); 45 | } else { 46 | $replyComment.slideUp(); 47 | } 48 | } 49 | }; 50 | }, 51 | __commentFn = function(err, $comment) { 52 | var comment = $comment.data("comment"); 53 | if (comment.username !== curUser) { 54 | $comment.find("span[type='remove']").parent().remove(); 55 | } 56 | if (!curUser) { 57 | $comment.find("span[type='reply']").parent().remove(); 58 | } 59 | $(document).trigger("admire.draw", [$comment.data("comment").id, $comment.find(".u-admire").parent()]); 60 | }; 61 | if (err) { 62 | $comments.find(".u-more").text("获取评论失败"); 63 | return; 64 | } 65 | for (i = 0, m = comments.length; i < m; i++) { 66 | container = $("
").appendTo(commentContainer); 67 | $(document).trigger("comment.drawOne", [comments[i], container, [{ 68 | html: "删除", 69 | click: __deleteFn(container) 70 | }, { 71 | html: "回复", 72 | click: __replyFn(container) 73 | }, { 74 | html: "" 75 | }], 76 | __commentFn 77 | ]); 78 | } 79 | $comments.attr("cur", curPage + 1); 80 | if (comments.length < perPage) { 81 | $comments.find(".u-more").text("没有更多评论了"); 82 | } else { 83 | $comments.find(".u-more").text("点击获取更多评论").bind("click", __readMoreComments); 84 | } 85 | 86 | } 87 | ]); 88 | }; 89 | $('pre code').each(function(i, e) { 90 | hljs.highlightBlock(e); 91 | }); 92 | 93 | $("#clearComment").click(function(event) { 94 | $("#newComments textarea").val(""); 95 | }); 96 | 97 | $(document).trigger("tag.drawArticleTags", [$("#tags").attr("aid"), $("#tags")]); 98 | 99 | $(".g-art-info .u-avatar").each(function() { 100 | $(document).trigger("user.draw", [$(this).attr("uid"), $(this), 101 | function(err, $user) { 102 | if (err) return; 103 | var user = $user.data("user"); 104 | $(".g-art-info .u-nick").text(user.nickname); 105 | } 106 | ]); 107 | }); 108 | 109 | $comments.find(".u-more").click(__readMoreComments).click(); 110 | 111 | $(document).trigger("bookmark.draw", [$bookmark.attr("aid"), $bookmark, $bookmark.attr("cur")]); 112 | 113 | $("#deleteArticle").click(function(event) { 114 | $(document).trigger("article.remove", [$(this).attr("aid"), function(err){ 115 | if(err) return; 116 | window.location.href = "/article_list"; 117 | }]); 118 | }); 119 | }(jQuery, window)); -------------------------------------------------------------------------------- /public/javascripts/pageTools/Remind.js: -------------------------------------------------------------------------------- 1 | (function($, window) { 2 | var emitter = $(document); 3 | emitter.bind({ 4 | "remind.remove": function(event, remindId, fnCallback) { 5 | $.ajax({ 6 | url: "/nor/remind_remove", 7 | type: "post", 8 | dataType: "json", 9 | data: { 10 | remindId: remindId 11 | } 12 | }).done(function(data) { 13 | if (typeof fnCallback === "function") fnCallback(null); 14 | }).fail(function(err) { 15 | if (typeof fnCallback === "function") fnCallback(err); 16 | }); 17 | }, 18 | "remind.countAll": function(event, fnCallback) { 19 | $.ajax({ 20 | url: "/nor/remind_countAll", 21 | type: "post", 22 | dataType: "json" 23 | }).done(function(data) { 24 | if (typeof fnCallback === "function") fnCallback(null, data.total); 25 | }).fail(function(err) { 26 | if (typeof fnCallback === "function") fnCallback(err); 27 | }); 28 | }, 29 | "remind.countByType": function(event, type, fnCallback) { 30 | $.ajax({ 31 | url: "/nor/remind_countByType", 32 | type: "post", 33 | dataType: "json", 34 | data: { 35 | type: type 36 | } 37 | }).done(function(data) { 38 | if (typeof fnCallback === "function") fnCallback(null, data.total); 39 | }).fail(function(err) { 40 | if (typeof fnCallback === "function") fnCallback(err); 41 | }); 42 | }, 43 | "remind.getAll": function(event, curPage, perPage, fnCallback) { 44 | $.ajax({ 45 | url: "/nor/remind_getAll", 46 | type: "post", 47 | dataType: "json", 48 | data: { 49 | curPage: curPage, 50 | perPage: perPage 51 | } 52 | }).done(function(data) { 53 | if (typeof fnCallback === "function") fnCallback(null, data.reminds); 54 | }).fail(function(err) { 55 | if (typeof fnCallback === "function") fnCallback(err); 56 | }); 57 | }, 58 | "remind.getByType": function(event, type, curPage, perPage, fnCallback) { 59 | $.ajax({ 60 | url: "/nor/remind_getByType", 61 | type: "post", 62 | dataType: "json", 63 | data: { 64 | type: type, 65 | curPage: curPage, 66 | perPage: perPage 67 | } 68 | }).done(function(data) { 69 | if (typeof fnCallback === "function") fnCallback(null, data.reminds); 70 | }).fail(function(err) { 71 | if (typeof fnCallback === "function") fnCallback(err); 72 | }); 73 | }, 74 | "remind.setReaded": function(event, remindIds, fnCallback) { 75 | $.ajax({ 76 | url: "/nor/remind_setReaded", 77 | type: "post", 78 | dataType: "json", 79 | data: { 80 | remindIds: remindIds 81 | } 82 | }).done(function(data) { 83 | if (typeof fnCallback === "function") fnCallback(null); 84 | }).fail(function(err) { 85 | if (typeof fnCallback === "function") fnCallback(err); 86 | }); 87 | }, 88 | "remind.draw": function(event, container, fnCallback) { 89 | container.addClass("b-remind-loading"); 90 | var i, 91 | __countComment = function(err, total) { 92 | if (err) { 93 | if (typeof fnCallback === "function") fnCallback(err); 94 | return; 95 | } 96 | var $li = $("
  • "); 97 | if (total) { 98 | $li.append(" " + total + " 条新回复"); 99 | container.append($li); 100 | } 101 | container.data("commentTotal", total); 102 | emitter.trigger("remind.countByType", ["bookmark", __countBookmark]); 103 | }, 104 | __countBookmark = function(err, total) { 105 | if (err) { 106 | if (typeof fnCallback === "function") fnCallback(err); 107 | return; 108 | } 109 | var $li = $("
  • "); 110 | if (total) { 111 | $li.append(" " + total + " 条新收藏消息"); 112 | container.append($li); 113 | } 114 | container.data("bookmarkTotal", total); 115 | emitter.trigger("remind.countByType", ["admire", __countAdmire]); 116 | }, 117 | __countAdmire = function(err, total) { 118 | if (err) { 119 | if (typeof fnCallback === "function") fnCallback(err); 120 | return; 121 | } 122 | var $li = $("
  • "); 123 | if (total) { 124 | $li.append(" " + total + " 条赞"); 125 | container.append($li); 126 | } 127 | container.data("admireTotal", total); 128 | container.removeClass("b-remind-loading"); 129 | if (typeof fnCallback === "function") fnCallback(null, container); 130 | }; 131 | emitter.trigger("remind.countByType", ["comment", __countComment]); 132 | } 133 | }); 134 | }(jQuery, window)); -------------------------------------------------------------------------------- /doc/comment.md: -------------------------------------------------------------------------------- 1 | 评论 [Comment] 2 | === 3 | *** 4 | #构造函数 5 | *** 6 | ```js 7 | function Comment(comment) { 8 | this.comment = comment.comment; 9 | this.username = comment.username; 10 | this.reply = comment.reply; 11 | this.articleId = comment.articleId; 12 | this.time = comment.time; 13 | this.id = comment.id; 14 | } 15 | ``` 16 | #属性 17 | *** 18 | > **id** [string] : 评论的id,uuid的v4生成 19 | 20 | > **articleId** [string] : 评论所属文章的id 21 | 22 | > **username** [string] : 创建这条评论的用户名 23 | 24 | > **comment** [string] : 评论的内容 25 | 26 | > **time** [number] : 评论的时间,为毫秒数 27 | 28 | > **reply** [string] : 若本评论回复了其他评论,则reply的值为被回复的评论的id,否则为null 29 | 30 | #原型上的方法 31 | *** 32 | ##save 33 | ### 说明 34 | > 保存当前评论对象 35 | 36 | ### 参数 37 | > **callback** [function] : 保存完成后的回调函数,两个参数 38 | 39 | >> **err** [object]: 若未发生错误,为null 40 | 41 | >> **result** [Comment] :持久化后的Comment对象 42 | 43 | 44 | ### 实例 45 | ```js 46 | var comment = new Comment({ 47 | articleId: req.body.articleId, 48 | username: req.session.user.username, 49 | comment: req.body.comment, 50 | reply: req.body.replyId 51 | }); 52 | comment.save(function(err, comment) { 53 | if (err) return callback(err); 54 | callback(err, comment); 55 | }); 56 | ``` 57 | 58 | ##remove 59 | ### 说明 60 | > 删除当前评论 61 | 62 | ### 参数 63 | > **callback** [function] : 删除完成后的回调函数,一个参数 64 | 65 | >> **err** [object]: 若未发生错误,为null 66 | 67 | 68 | ### 实例 69 | ```js 70 | comment.remove(function(err) { 71 | if (err) return res.json(500, { 72 | message: err.message 73 | }); 74 | res.json({ 75 | success: true 76 | }); 77 | }); 78 | ``` 79 | #方法 80 | *** 81 | ##get 82 | ### 说明 83 | > 根据评论id获取一个评论 84 | 85 | ### 参数 86 | > **commentId** [string] : 需要获取的评论的id 87 | 88 | > **callback** [function] : 获取完成后的回调函数,两个参数 89 | 90 | >> **err** [object]: 若未发生错误,为null 91 | 92 | >> **comment** [Comment]: 获取的评论对象,没有则为null 93 | 94 | 95 | ### 实例 96 | ```js 97 | Comment.get(req.body.commentId, function(err, comment) { 98 | //TODO 99 | }); 100 | ``` 101 | 102 | ##countByArticle 103 | ###说明 104 | > 统计一篇文章的评论个数 105 | 106 | ###参数 107 | > **articleId** [string]: 文章的id 108 | 109 | > **callback** [function] : 统计完成后的回调函数,两个参数 110 | 111 | >> **err** [object]: 若未发生错误,为null 112 | 113 | >> **total** [number]: 该文章的评论总数 114 | 115 | ###实例 116 | ```js 117 | Comment.countByArticle(req.body.articleId, function(err, total) { 118 | if (err) return res.json(500, { 119 | message: err.message 120 | }); 121 | res.json({ 122 | total: total 123 | }); 124 | }); 125 | ``` 126 | 127 | ##getByArticle 128 | ### 说明 129 | > 分页获取一篇文章的评论 130 | 131 | ### 参数 132 | > **articleId** [string] : 文章的id 133 | 134 | > **curPage** [integer] : 当前页,从0开始 135 | 136 | > **perPage** [integer] : 每页的文章个数 137 | 138 | > **callback** [function] : 获取完成后的回调函数,两个参数 139 | 140 | >> **err** [object]: 若未发生错误,为null 141 | 142 | >> **comments** [array:Comment]: 获取的评论对象列表 143 | 144 | ### 实例 145 | ```js 146 | Comment.getByArticle(req.body.articleId, Number(req.body.curPage), Number(req.body.perPage), function(err, comments) { 147 | var i; 148 | if (err) return res.json(500, { 149 | message: err.message 150 | }); 151 | for (i = comments.length; i--;) { 152 | comments[i].time = moment(comments[i].time).format("HH:mm MM月DD日 YYYY年"); 153 | } 154 | res.json({ 155 | comments: comments 156 | }); 157 | }); 158 | ``` 159 | 160 | ##countByUser 161 | ###说明 162 | > 统计一个用户的评论个数 163 | 164 | ###参数 165 | > **username** [string]: 用户的用户名 166 | 167 | > **callback** [function] : 统计完成后的回调函数,两个参数 168 | 169 | >> **err** [object]: 若未发生错误,为null 170 | 171 | >> **total** [number]: 该用户的评论总数 172 | 173 | ###实例 174 | ```js 175 | Comment.countByUser(req.body.username, function(err, total) { 176 | if (err) return res.json(500, { 177 | message: err.message 178 | }); 179 | res.json({ 180 | total: total 181 | }); 182 | }); 183 | ``` 184 | 185 | ##getByUser 186 | ### 说明 187 | > 分页获取一个用户的评论 188 | 189 | ### 参数 190 | > **username** [string]: 用户的用户名 191 | 192 | > **curPage** [integer] : 当前页,从0开始 193 | 194 | > **perPage** [integer] : 每页的文章个数 195 | 196 | > **callback** [function] : 获取完成后的回调函数,两个参数 197 | 198 | >> **err** [object]: 若未发生错误,为null 199 | 200 | >> **comments** [array:Comment]: 获取的评论对象列表 201 | 202 | ### 实例 203 | ```js 204 | Comment.getByUser(req.body.username, Number(req.body.curPage), Number(req.body.perPage), function(err, comments) { 205 | if (err) return res.json(500, { 206 | message: err.message 207 | }); 208 | for (var i = comments.length; i--;) { 209 | comments[i].time = moment(comments[i].time).format("HH:mm MM月DD日 YYYY年"); 210 | } 211 | res.json({ 212 | comments: comments 213 | }); 214 | }); 215 | ``` 216 | 217 | -------------------------------------------------------------------------------- /views/userCenter.jade: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | block style 4 | link(rel='stylesheet', href='/stylesheets/pageTools/comment.css') 5 | link(rel='stylesheet', href='/stylesheets/pageTools/tag.css') 6 | link(rel='stylesheet', href='/stylesheets/pageTools/admire.css') 7 | link(rel='stylesheet', href='/stylesheets/pageTools/user.css') 8 | link(rel='stylesheet', href='/stylesheets/pageTools/bookmark.css') 9 | link(rel='stylesheet', href='/stylesheets/pageTools/admire.css') 10 | link(rel='stylesheet', href='/stylesheets/pageTools/article.css') 11 | link(rel='stylesheet', href='/stylesheets/page/userCenter.css') 12 | 13 | block content 14 | input(type="hidden", id="user", uid=user?"#{user.username}":"") 15 | input(type="hidden", id="curUser", uid="#{curUser.username}") 16 | div(class='container') 17 | h1 18 | div(style="float:left") 19 | strong #{curUser.nickname}的用户中心 20 | div(class="u-avatar", style="float:right", id="user") 21 | div(class="clearfix") 22 | 23 | div(class="g-row") 24 | div(class="g-left") 25 | div(class="list-group u-panel", id="chosePanel", style="padding:0;border-radius:20px;overflow:hidden") 26 | if user&&curUser.username === user.username 27 | a(href="#", class="list-group-item", ref="getComments") 收到的评论 28 | span(class="badge pull-right remind-count", type="comment") 29 | a(href="#", class="list-group-item", ref="getAdmires") 收到的贊 30 | span(class="badge pull-right remind-count", type="admire") 31 | a(href="#", class="list-group-item", ref="getBookmarks") 收到的收藏 32 | span(class="badge pull-right remind-count", type="bookmark") 33 | if curUser.owner 34 | a(href="#", class="list-group-item", ref="articles") 35 | if user&&curUser.username === user.username 36 | != "我的文章" 37 | else 38 | != "ta的文章" 39 | span(class="badge pull-right article-count") 40 | a(href="#", class=curUser.owner?"list-group-item" : "list-group-item active", ref="bookmarks") 41 | if user&&curUser.username === user.username 42 | != "我的收藏" 43 | else 44 | != "ta的收藏" 45 | span(class="badge pull-right bookmark-count") 46 | a(href="#", class="list-group-item", ref="comments") 47 | if user&&curUser.username === user.username 48 | != "我的评论" 49 | else 50 | != "ta的评论" 51 | span(class="badge pull-right comment-count") 52 | a(href="#", class="list-group-item", ref="admires") 53 | if user&&curUser.username === user.username 54 | != "我赞过的评论" 55 | else 56 | != "ta赞过的评论" 57 | span(class="badge pull-right admire-count") 58 | div(class="u-panel", style="margin-top:20px") 59 | if user&&curUser.username === user.username 60 | != "我的标签:" 61 | else 62 | != "ta的标签:" 63 | div(id="tags") 64 | div(class="g-right") 65 | div(class="u-panel") 66 | if user&&curUser.username === user.username 67 | div(class="panel-body g-hide", id="getComments", cur="0") 68 | div(class="g-container") 69 | div(class="u-more") 点击加载更多 70 | div(class="panel-body g-hide", id="getAdmires", cur="0") 71 | div(class="g-container") 72 | div(class="u-more") 点击加载更多 73 | div(class="panel-body g-hide", id="getBookmarks", cur="0") 74 | div(class="g-container") 75 | div(class="u-more") 点击加载更多 76 | if curUser.owner 77 | div(class="panel-body g-hide", id="articles", cur="0") 78 | div(class="g-container") 79 | div(class="u-more") 点击加载更多 80 | 81 | div(class="panel-body g-hide", id="comments", cur="0") 82 | div(class="g-container") 83 | div(class="u-more") 点击加载更多 84 | div(class="panel-body g-hide", id="bookmarks", cur="0") 85 | div(class="g-container") 86 | div(class="u-more") 点击加载更多 87 | div(class="panel-body g-hide", id="admires", cur="0") 88 | div(class="g-container") 89 | div(class="u-more") 点击加载更多 90 | 91 | block javascript 92 | script(type='text/javascript', src='/javascripts/pageTools/User.js') 93 | script(type='text/javascript', src='/javascripts/pageTools/Tag.js') 94 | script(type='text/javascript', src='/javascripts/pageTools/Article.js') 95 | script(type='text/javascript', src='/javascripts/pageTools/Bookmark.js') 96 | script(type='text/javascript', src='/javascripts/pageTools/Admire.js') 97 | script(type='text/javascript', src='/javascripts/pageTools/Comment.js') 98 | script(type='text/javascript', src='/javascripts/page/userCenter.js') -------------------------------------------------------------------------------- /routes/Actions/ArticleAction.js: -------------------------------------------------------------------------------- 1 | var Article = require("../Model/Article.js"), 2 | User = require("../Model/User.js"), 3 | Comment = require("../Model/Comment.js"), 4 | Admire = require("../Model/Admire.js"), 5 | Bookmark = require("../Model/Bookmark.js"), 6 | Tag = require("../Model/Tag.js"), 7 | async = require("async"), 8 | markdown = require("markdown").markdown, 9 | moment = require("moment"); 10 | 11 | moment.lang("zh-cn"); 12 | 13 | exports.writePage = function(req, res) { 14 | res.render("writeArticle"); 15 | }; 16 | 17 | exports.editPage = function(req, res) { 18 | Article.get(req.query.articleId, function(err, article) { 19 | if (err) { 20 | return res.render("error", { 21 | message: err.message 22 | }); 23 | } 24 | if (article) { 25 | return res.render("editArticle", { 26 | article: article 27 | }); 28 | } 29 | }); 30 | }; 31 | 32 | exports.update = function(req, res) { 33 | Article.get(req.body.articleId, function(err, article) { 34 | if (err) return res.json(500, { 35 | message: err.message 36 | }); 37 | article.title = req.body.title; 38 | article.content = req.body.content; 39 | article.tags = JSON.parse(req.body.tags); 40 | article.update(function(err) { 41 | if (err) return res.json(500, { 42 | message: err.message 43 | }); 44 | res.json("editArticle", { 45 | success: true 46 | }); 47 | }); 48 | }); 49 | }; 50 | 51 | exports.save = function(req, res) { 52 | console.log(req.body.tags); 53 | var article = new Article({ 54 | writer: req.session.user.username, 55 | content: req.body.content, 56 | title: req.body.title, 57 | tags: JSON.parse(req.body.tags) 58 | }); 59 | article.save(function(err, art) { 60 | if (err) return res.render("err", { 61 | message: "保存文章失败" 62 | }); 63 | res.redirect("/article_load?articleId=" + art.id); 64 | }); 65 | }; 66 | 67 | exports.remove = function(req, res) { 68 | Article.get(req.body.articleId, function(err, article) { 69 | if (err) return res.json(500); 70 | article.remove(function(err) { 71 | if (err) return res.json(500); 72 | res.json({}); 73 | }); 74 | }); 75 | }; 76 | 77 | exports.getOne = function(req, res) { 78 | Article.get(req.body.articleId, function(err, article) { 79 | if (err) return res.json(500); 80 | if (!article) return res.status(404).send("not fount"); 81 | article.writeTime = moment(article.writeTime).fromNow(); 82 | return res.json({ 83 | article: article 84 | }); 85 | }); 86 | }; 87 | 88 | 89 | exports.load = function(req, res) { 90 | Article.get(req.query.articleId, function(err, article) { 91 | if (err) return res.render("error", { 92 | message: err.message 93 | }); 94 | if (!article) return res.render("error", { 95 | message: "没有找到该文章" 96 | }); 97 | article.content = markdown.toHTML(article.content); 98 | article.writeTime = moment(article.writeTime).fromNow(); 99 | res.render("articleDetail", { 100 | article: article 101 | }); 102 | }); 103 | }; 104 | 105 | exports.listAll = function(req, res) { 106 | Article.getAll(Number(req.body.curPage), Number(req.body.perPage), function(err, articles) { 107 | var i; 108 | if (err) return res.json(500); 109 | for (i = articles.length; i--;) { 110 | articles[i].content = markdown.toHTML(articles[i].content); 111 | articles[i].writeTime = moment(articles[i].writeTime).fromNow(); 112 | } 113 | return res.json({ 114 | articles: articles 115 | }); 116 | }); 117 | }; 118 | 119 | 120 | exports.getByUser = function(req, res) { 121 | Article.getByUser(req.body.username, Number(req.body.curPage), Number(req.body.perPage), function(err, articles) { 122 | if (err) return res.json(500); 123 | for (var i = articles.length; i--;) { 124 | articles[i].writeTime = moment(articles[i].writeTime).fromNow(); 125 | } 126 | res.json({ 127 | articles: articles 128 | }); 129 | }); 130 | }; 131 | 132 | exports.countByUser = function(req, res) { 133 | Article.countByUser(req.body.username, function(err, total) { 134 | if (err) return res.json(500); 135 | res.json({ 136 | total: total 137 | }); 138 | }); 139 | }; 140 | 141 | exports.getByTags = function(req, res) { 142 | Article.getByTags(req.body.tags, Number(req.body.curPage), Number(req.body.perPage), function(err, articles) { 143 | if (err) return res.json(500); 144 | for (var i = articles.length; i--;) { 145 | articles[i].writeTime = moment(articles[i].writeTime).fromNow(); 146 | } 147 | res.json({ 148 | articles: articles 149 | }); 150 | }); 151 | }; 152 | 153 | exports.getByTitle = function(req, res) { 154 | Article.getByTitle(req.body.title, Number(req.body.curPage), Number(req.body.perPage), function(err, articles) { 155 | if (err) return res.json(500); 156 | for (var i = articles.length; i--;) { 157 | articles[i].writeTime = moment(articles[i].writeTime).fromNow(); 158 | } 159 | res.json({ 160 | articles: articles 161 | }); 162 | }); 163 | }; --------------------------------------------------------------------------------