├── .gitignore ├── public ├── js │ ├── topic.js │ └── tinymce │ │ ├── skins │ │ ├── ui │ │ │ ├── oxide │ │ │ │ ├── fonts │ │ │ │ │ └── tinymce-mobile.woff │ │ │ │ ├── content.mobile.min.css │ │ │ │ └── content.mobile.css │ │ │ └── oxide-dark │ │ │ │ ├── fonts │ │ │ │ └── tinymce-mobile.woff │ │ │ │ ├── content.mobile.min.css │ │ │ │ └── content.mobile.css │ │ └── content │ │ │ ├── default │ │ │ ├── content.min.css │ │ │ └── content.css │ │ │ ├── writer │ │ │ ├── content.min.css │ │ │ └── content.css │ │ │ ├── document │ │ │ ├── content.min.css │ │ │ └── content.css │ │ │ └── dark │ │ │ ├── content.min.css │ │ │ └── content.css │ │ ├── plugins │ │ ├── hr │ │ │ ├── index.js │ │ │ ├── plugin.min.js │ │ │ └── plugin.js │ │ ├── toc │ │ │ ├── index.js │ │ │ └── plugin.min.js │ │ ├── code │ │ │ ├── index.js │ │ │ ├── plugin.min.js │ │ │ └── plugin.js │ │ ├── help │ │ │ └── index.js │ │ ├── image │ │ │ └── index.js │ │ ├── link │ │ │ └── index.js │ │ ├── lists │ │ │ └── index.js │ │ ├── media │ │ │ └── index.js │ │ ├── paste │ │ │ └── index.js │ │ ├── print │ │ │ ├── index.js │ │ │ ├── plugin.min.js │ │ │ └── plugin.js │ │ ├── save │ │ │ ├── index.js │ │ │ ├── plugin.min.js │ │ │ └── plugin.js │ │ ├── table │ │ │ └── index.js │ │ ├── anchor │ │ │ ├── index.js │ │ │ ├── plugin.min.js │ │ │ └── plugin.js │ │ ├── bbcode │ │ │ ├── index.js │ │ │ ├── plugin.min.js │ │ │ └── plugin.js │ │ ├── advlist │ │ │ ├── index.js │ │ │ └── plugin.min.js │ │ ├── charmap │ │ │ └── index.js │ │ ├── preview │ │ │ ├── index.js │ │ │ ├── plugin.min.js │ │ │ └── plugin.js │ │ ├── autolink │ │ │ ├── index.js │ │ │ └── plugin.min.js │ │ ├── autosave │ │ │ ├── index.js │ │ │ └── plugin.min.js │ │ ├── fullpage │ │ │ └── index.js │ │ ├── tabfocus │ │ │ ├── index.js │ │ │ ├── plugin.min.js │ │ │ └── plugin.js │ │ ├── template │ │ │ └── index.js │ │ ├── autoresize │ │ │ ├── index.js │ │ │ └── plugin.min.js │ │ ├── codesample │ │ │ └── index.js │ │ ├── emoticons │ │ │ └── index.js │ │ ├── fullscreen │ │ │ └── index.js │ │ ├── imagetools │ │ │ └── index.js │ │ ├── importcss │ │ │ ├── index.js │ │ │ └── plugin.min.js │ │ ├── pagebreak │ │ │ ├── index.js │ │ │ ├── plugin.min.js │ │ │ └── plugin.js │ │ ├── quickbars │ │ │ └── index.js │ │ ├── textcolor │ │ │ ├── index.js │ │ │ ├── plugin.min.js │ │ │ └── plugin.js │ │ ├── wordcount │ │ │ └── index.js │ │ ├── colorpicker │ │ │ ├── index.js │ │ │ ├── plugin.min.js │ │ │ └── plugin.js │ │ ├── contextmenu │ │ │ ├── index.js │ │ │ ├── plugin.min.js │ │ │ └── plugin.js │ │ ├── nonbreaking │ │ │ ├── index.js │ │ │ ├── plugin.min.js │ │ │ └── plugin.js │ │ ├── noneditable │ │ │ ├── index.js │ │ │ ├── plugin.min.js │ │ │ └── plugin.js │ │ ├── textpattern │ │ │ └── index.js │ │ ├── visualchars │ │ │ └── index.js │ │ ├── legacyoutput │ │ │ ├── index.js │ │ │ └── plugin.min.js │ │ ├── spellchecker │ │ │ └── index.js │ │ ├── visualblocks │ │ │ ├── index.js │ │ │ ├── plugin.min.js │ │ │ └── plugin.js │ │ ├── searchreplace │ │ │ └── index.js │ │ ├── directionality │ │ │ ├── index.js │ │ │ └── plugin.min.js │ │ └── insertdatetime │ │ │ ├── index.js │ │ │ └── plugin.min.js │ │ ├── icons │ │ └── default │ │ │ └── index.js │ │ ├── themes │ │ ├── mobile │ │ │ └── index.js │ │ └── silver │ │ │ └── index.js │ │ ├── bower.json │ │ ├── composer.json │ │ ├── package.json │ │ └── readme.md ├── images │ ├── icons │ │ ├── talk.png │ │ ├── 753890-32.png │ │ ├── forum-32.png │ │ ├── timeIcon.png │ │ ├── user64px.png │ │ ├── 1954526-16.png │ │ ├── courseIcon.png │ │ ├── homeworkIcon.png │ │ ├── infoIcon24px.png │ │ ├── studentIcon.png │ │ ├── followIcon20px.png │ │ ├── followIcon24px.png │ │ ├── followIcon32px.png │ │ ├── universityIcon.png │ │ ├── announcementIcon.png │ │ ├── iconProfile24px.png │ │ ├── iconWriteAnswer.png │ │ ├── unfollowIcon20px.png │ │ ├── unfollowIcon24px.png │ │ ├── unfollowIcon32px.png │ │ ├── universityTitleIcon.png │ │ ├── iconfinder_blog_46779.png │ │ ├── iconfinder_info-blog_46810.png │ │ ├── iconfinder_profile-filled_299075.png │ │ ├── iconfinder_resolutions-08_897241.png │ │ ├── iconfinder_Closed_Book_Icon_1741323.png │ │ ├── liked.svg │ │ ├── not-liked.svg │ │ ├── unfollow.svg │ │ └── follow.svg │ ├── readme-images │ │ ├── 1.png │ │ ├── 2.png │ │ ├── 3.png │ │ ├── 4.png │ │ ├── 5.png │ │ ├── 6.png │ │ ├── 7.png │ │ ├── 8.png │ │ ├── 9.png │ │ ├── EOL.PNG │ │ └── mongo-import-error.PNG │ ├── topics-images │ │ ├── image-1593676345129.jpg │ │ ├── image-1593676576143.png │ │ ├── image-1593676829053.png │ │ ├── image-1593677023678.png │ │ ├── image-1593677078061.png │ │ ├── image-1593677344763.png │ │ ├── image-1593680383311.png │ │ ├── image-1593680415229.png │ │ ├── image-1593680454047.png │ │ ├── image-1593680500501.png │ │ ├── image-1593680595679.jpg │ │ ├── image-1593676282305.jpeg │ │ └── image-1593680561250.jpeg │ └── users-images │ │ ├── image-1593680158999.jpg │ │ ├── image-1593680220764.jpg │ │ ├── image-1593680262984.jpg │ │ └── image-1593680290286.jpg ├── webfonts │ ├── fa-brands-400.eot │ ├── fa-brands-400.ttf │ ├── fa-solid-900.eot │ ├── fa-solid-900.ttf │ ├── fa-solid-900.woff │ ├── fa-brands-400.woff │ ├── fa-brands-400.woff2 │ ├── fa-regular-400.eot │ ├── fa-regular-400.ttf │ ├── fa-regular-400.woff │ ├── fa-regular-400.woff2 │ └── fa-solid-900.woff2 └── css │ ├── modified-bootstrap.css │ └── style.css ├── views ├── partials │ ├── profile-includes │ │ ├── inc.hbs │ │ ├── user-questions.hbs │ │ ├── user-answers.hbs │ │ ├── user-topics-followed.hbs │ │ └── user-topics-created.hbs │ └── navbar.hbs ├── error.hbs ├── layouts │ └── main.hbs ├── login.hbs ├── write-answer.hbs ├── ask-question.hbs ├── create-topic.hbs ├── register.hbs └── profile.hbs ├── .dockerignore ├── Dockerfile ├── routes ├── answersRoutes.js ├── questionsRoutes.js ├── usersRoutes.js └── topicsRoutes.js ├── .env ├── mongodb-database └── import-data.sh ├── models ├── answer.js ├── question.js ├── topic.js └── user.js ├── package.json ├── utils └── passport-config.js ├── controllers ├── answersController.js ├── questionsController.js └── usersController.js ├── bin └── www ├── docker-compose.yaml └── server.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ -------------------------------------------------------------------------------- /public/js/topic.js: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /views/partials/profile-includes/inc.hbs: -------------------------------------------------------------------------------- 1 | alae -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .gitignore 3 | .git/ 4 | Dockerfile -------------------------------------------------------------------------------- /public/images/icons/talk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/icons/talk.png -------------------------------------------------------------------------------- /public/images/icons/753890-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/icons/753890-32.png -------------------------------------------------------------------------------- /public/images/icons/forum-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/icons/forum-32.png -------------------------------------------------------------------------------- /public/images/icons/timeIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/icons/timeIcon.png -------------------------------------------------------------------------------- /public/images/icons/user64px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/icons/user64px.png -------------------------------------------------------------------------------- /public/images/readme-images/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/readme-images/1.png -------------------------------------------------------------------------------- /public/images/readme-images/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/readme-images/2.png -------------------------------------------------------------------------------- /public/images/readme-images/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/readme-images/3.png -------------------------------------------------------------------------------- /public/images/readme-images/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/readme-images/4.png -------------------------------------------------------------------------------- /public/images/readme-images/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/readme-images/5.png -------------------------------------------------------------------------------- /public/images/readme-images/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/readme-images/6.png -------------------------------------------------------------------------------- /public/images/readme-images/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/readme-images/7.png -------------------------------------------------------------------------------- /public/images/readme-images/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/readme-images/8.png -------------------------------------------------------------------------------- /public/images/readme-images/9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/readme-images/9.png -------------------------------------------------------------------------------- /public/webfonts/fa-brands-400.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/webfonts/fa-brands-400.eot -------------------------------------------------------------------------------- /public/webfonts/fa-brands-400.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/webfonts/fa-brands-400.ttf -------------------------------------------------------------------------------- /public/webfonts/fa-solid-900.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/webfonts/fa-solid-900.eot -------------------------------------------------------------------------------- /public/webfonts/fa-solid-900.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/webfonts/fa-solid-900.ttf -------------------------------------------------------------------------------- /public/webfonts/fa-solid-900.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/webfonts/fa-solid-900.woff -------------------------------------------------------------------------------- /public/images/icons/1954526-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/icons/1954526-16.png -------------------------------------------------------------------------------- /public/images/icons/courseIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/icons/courseIcon.png -------------------------------------------------------------------------------- /public/images/icons/homeworkIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/icons/homeworkIcon.png -------------------------------------------------------------------------------- /public/images/icons/infoIcon24px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/icons/infoIcon24px.png -------------------------------------------------------------------------------- /public/images/icons/studentIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/icons/studentIcon.png -------------------------------------------------------------------------------- /public/images/readme-images/EOL.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/readme-images/EOL.PNG -------------------------------------------------------------------------------- /public/webfonts/fa-brands-400.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/webfonts/fa-brands-400.woff -------------------------------------------------------------------------------- /public/webfonts/fa-brands-400.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/webfonts/fa-brands-400.woff2 -------------------------------------------------------------------------------- /public/webfonts/fa-regular-400.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/webfonts/fa-regular-400.eot -------------------------------------------------------------------------------- /public/webfonts/fa-regular-400.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/webfonts/fa-regular-400.ttf -------------------------------------------------------------------------------- /public/webfonts/fa-regular-400.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/webfonts/fa-regular-400.woff -------------------------------------------------------------------------------- /public/webfonts/fa-regular-400.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/webfonts/fa-regular-400.woff2 -------------------------------------------------------------------------------- /public/webfonts/fa-solid-900.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/webfonts/fa-solid-900.woff2 -------------------------------------------------------------------------------- /public/images/icons/followIcon20px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/icons/followIcon20px.png -------------------------------------------------------------------------------- /public/images/icons/followIcon24px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/icons/followIcon24px.png -------------------------------------------------------------------------------- /public/images/icons/followIcon32px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/icons/followIcon32px.png -------------------------------------------------------------------------------- /public/images/icons/universityIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/icons/universityIcon.png -------------------------------------------------------------------------------- /public/images/icons/announcementIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/icons/announcementIcon.png -------------------------------------------------------------------------------- /public/images/icons/iconProfile24px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/icons/iconProfile24px.png -------------------------------------------------------------------------------- /public/images/icons/iconWriteAnswer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/icons/iconWriteAnswer.png -------------------------------------------------------------------------------- /public/images/icons/unfollowIcon20px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/icons/unfollowIcon20px.png -------------------------------------------------------------------------------- /public/images/icons/unfollowIcon24px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/icons/unfollowIcon24px.png -------------------------------------------------------------------------------- /public/images/icons/unfollowIcon32px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/icons/unfollowIcon32px.png -------------------------------------------------------------------------------- /public/images/icons/universityTitleIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/icons/universityTitleIcon.png -------------------------------------------------------------------------------- /public/images/icons/iconfinder_blog_46779.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/icons/iconfinder_blog_46779.png -------------------------------------------------------------------------------- /public/css/modified-bootstrap.css: -------------------------------------------------------------------------------- 1 | .card-link { 2 | color: #445252; 3 | } 4 | 5 | .card-link:hover { 6 | text-decoration: none; 7 | color: blue; 8 | } 9 | -------------------------------------------------------------------------------- /public/images/icons/iconfinder_info-blog_46810.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/icons/iconfinder_info-blog_46810.png -------------------------------------------------------------------------------- /public/images/readme-images/mongo-import-error.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/readme-images/mongo-import-error.PNG -------------------------------------------------------------------------------- /public/images/topics-images/image-1593676345129.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/topics-images/image-1593676345129.jpg -------------------------------------------------------------------------------- /public/images/topics-images/image-1593676576143.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/topics-images/image-1593676576143.png -------------------------------------------------------------------------------- /public/images/topics-images/image-1593676829053.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/topics-images/image-1593676829053.png -------------------------------------------------------------------------------- /public/images/topics-images/image-1593677023678.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/topics-images/image-1593677023678.png -------------------------------------------------------------------------------- /public/images/topics-images/image-1593677078061.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/topics-images/image-1593677078061.png -------------------------------------------------------------------------------- /public/images/topics-images/image-1593677344763.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/topics-images/image-1593677344763.png -------------------------------------------------------------------------------- /public/images/topics-images/image-1593680383311.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/topics-images/image-1593680383311.png -------------------------------------------------------------------------------- /public/images/topics-images/image-1593680415229.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/topics-images/image-1593680415229.png -------------------------------------------------------------------------------- /public/images/topics-images/image-1593680454047.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/topics-images/image-1593680454047.png -------------------------------------------------------------------------------- /public/images/topics-images/image-1593680500501.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/topics-images/image-1593680500501.png -------------------------------------------------------------------------------- /public/images/topics-images/image-1593680595679.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/topics-images/image-1593680595679.jpg -------------------------------------------------------------------------------- /public/images/users-images/image-1593680158999.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/users-images/image-1593680158999.jpg -------------------------------------------------------------------------------- /public/images/users-images/image-1593680220764.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/users-images/image-1593680220764.jpg -------------------------------------------------------------------------------- /public/images/users-images/image-1593680262984.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/users-images/image-1593680262984.jpg -------------------------------------------------------------------------------- /public/images/users-images/image-1593680290286.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alae-touba/social-network/HEAD/public/images/users-images/image-1593680290286.jpg -------------------------------------------------------------------------------- /views/error.hbs: -------------------------------------------------------------------------------- 1 |
{{error.stack}}
5 |
6 | | questions | 8 |
|---|
|
15 |
16 | {{this.content}} 17 | 18 | 19 | {{this.answers.length}} answer(s) 20 | |
21 |
26 |
27 |
35 | |
36 |
| answers | 6 |
|---|
|
13 |
14 | {{this.question.content}} 15 | 16 |{{{this.content}}} 19 | |
20 |
25 |
26 |
34 | |
35 |
| topics followed | 6 |
|---|
|
13 |
14 |
19 | |
20 |
24 |
25 |
33 | |
34 | {{/if}}
35 |
36 |
37 |
38 |
| topics created | 6 |
|---|
|
13 |
14 |
19 | |
20 |
26 |
27 |
35 | |
36 |
"+g()+"
"):a.insertContent(g())}),(n=e).ui.registry.addButton("pagebreak",{icon:"page-break",tooltip:"Page break",onAction:function(){return n.execCommand("mcePageBreak")}}),n.ui.registry.addMenuItem("pagebreak",{text:"Page break",icon:"page-break",onAction:function(){return n.execCommand("mcePageBreak")}}),i=(o=e).getParam("pagebreak_separator","\x3c!-- pagebreak --\x3e"),t=new RegExp(i.replace(/[\?\.\*\[\]\(\)\{\}\+\^\$\:]/g,function(e){return"\\"+e}),"gi"),o.on("BeforeSetContent",function(e){e.content=e.content.replace(t,g())}),o.on("PreInit",function(){o.serializer.addNodeFilter("img",function(e){for(var a,n,t=e.length;t--;)if((n=(a=e[t]).attr("class"))&&-1!==n.indexOf("mce-pagebreak")){var r=a.parent;if(o.schema.getBlockElements()[r.name]&&o.getParam("pagebreak_split_block",!1)){r.type=3,r.value=i,r.raw=!0,a.remove();continue}a.type=3,a.value=i,a.raw=!0}})}),(r=e).on("ResolveName",function(e){"IMG"===e.target.nodeName&&r.dom.hasClass(e.target,c())&&(e.name="pagebreak")})})}()}(); -------------------------------------------------------------------------------- /public/js/tinymce/plugins/save/plugin.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Tiny Technologies, Inc. All rights reserved. 3 | * Licensed under the LGPL or a commercial license. 4 | * For LGPL see License.txt in the project root for license information. 5 | * For commercial licenses see https://www.tiny.cloud/ 6 | * 7 | * Version: 5.3.2 (2020-06-10) 8 | */ 9 | !function(){"use strict";var e=tinymce.util.Tools.resolve("tinymce.PluginManager"),o=tinymce.util.Tools.resolve("tinymce.dom.DOMUtils"),a=tinymce.util.Tools.resolve("tinymce.util.Tools"),i=function(e){return e.getParam("save_enablewhendirty",!0)},c=function(e,n){e.notificationManager.open({text:n,type:"error"})},t=function(t){t.addCommand("mceSave",function(){!function(e){var n;if(n=o.DOM.getParent(e.id,"form"),!i(e)||e.isDirty()){if(e.save(),e.getParam("save_onsavecallback"))return e.execCallback("save_onsavecallback",e),e.nodeChanged();n?(e.setDirty(!1),n.onsubmit&&!n.onsubmit()||("function"==typeof n.submit?n.submit():c(e,"Error: Form submit field collision.")),e.nodeChanged()):c(e,"Error: No form element found.")}}(t)}),t.addCommand("mceCancel",function(){var e,n;e=t,n=a.trim(e.startContent),e.getParam("save_oncancelcallback")?e.execCallback("save_oncancelcallback",e):e.resetContent(n)})},r=function(t){return function(e){var n=function(){e.setDisabled(i(t)&&!t.isDirty())};return t.on("NodeChange dirty",n),function(){return t.off("NodeChange dirty",n)}}};!function n(){e.add("save",function(e){var n;(n=e).ui.registry.addButton("save",{icon:"save",tooltip:"Save",disabled:!0,onAction:function(){return n.execCommand("mceSave")},onSetup:r(n)}),n.ui.registry.addButton("cancel",{icon:"cancel",tooltip:"Cancel",disabled:!0,onAction:function(){return n.execCommand("mceCancel")},onSetup:r(n)}),n.addShortcut("Meta+S","","mceSave"),t(e)})}()}(); -------------------------------------------------------------------------------- /routes/topicsRoutes.js: -------------------------------------------------------------------------------- 1 | const express = require("express") 2 | const router = express.Router() 3 | const topicsController = require("./../controllers/topicsController") 4 | const usersController = require("./../controllers/usersController") 5 | const Topic = require("../models/topic") 6 | 7 | router.get("/", topicsController.getAllTopics) 8 | 9 | router.get("/create", usersController.checkAuthenticated, topicsController.getCreateTopicPage) 10 | router.post( 11 | "/create", 12 | usersController.checkAuthenticated, 13 | topicsController.upload.single("image"), 14 | topicsController.validateTopicFormFields(), 15 | topicsController.handleCreateTopic 16 | ) 17 | 18 | router.get("/:id", topicsController.getTopicById) 19 | 20 | router.get("/:id/question", usersController.checkAuthenticated, topicsController.getAskQuestionPage) 21 | router.post("/:id/question", usersController.checkAuthenticated, topicsController.validateQuestionFormFields(), topicsController.handleCreateQuestion) 22 | 23 | router.post("/follow-unfollow", usersController.checkAuthenticated, topicsController.handleFollowingUnfollowing) 24 | 25 | /* 26 | these two routes are for when we want to search a topic by name 27 | in the js script in the topics.hbs file i have a form, and when the user enters something, i will make a GET request to 28 | /topics/api/search/entered-value 29 | 30 | so there is two scenarios: 31 | if the text entered by the user is blank (empty string), a GET request will be made to: /topics/api/search/ 32 | if the text entered by the user is not blank, a GET request will be made to: /topics/api/search/entered-value 33 | */ 34 | router.get("/api/search", topicsController.handleTopicSearch1) 35 | router.get("/api/search/:searchInput", topicsController.handleTopicSearch2) 36 | 37 | module.exports = router 38 | -------------------------------------------------------------------------------- /public/js/tinymce/plugins/noneditable/plugin.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Tiny Technologies, Inc. All rights reserved. 3 | * Licensed under the LGPL or a commercial license. 4 | * For LGPL see License.txt in the project root for license information. 5 | * For commercial licenses see https://www.tiny.cloud/ 6 | * 7 | * Version: 5.3.2 (2020-06-10) 8 | */ 9 | !function(){"use strict";var t=tinymce.util.Tools.resolve("tinymce.PluginManager"),l=tinymce.util.Tools.resolve("tinymce.util.Tools"),u=function(t){return t.getParam("noneditable_noneditable_class","mceNonEditable")},f=function(n){return function(t){return-1!==(" "+t.attr("class")+" ").indexOf(n)}},s=function(i,o,c){return function(t){var n=arguments,e=n[n.length-2],r=0dont have an account?
33 | 34 |]*>/gi, '[quote]'); 47 | rep(/<\/blockquote>/gi, '[/quote]'); 48 | rep(/
/gi, '\n'); 49 | rep(/
/gi, '\n'); 50 | rep(/
/gi, '\n'); 51 | rep(//gi, ''); 52 | rep(/<\/p>/gi, '\n'); 53 | rep(/ |\u00a0/gi, ' '); 54 | rep(/"/gi, '"'); 55 | rep(/</gi, '<'); 56 | rep(/>/gi, '>'); 57 | rep(/&/gi, '&'); 58 | return s; 59 | }; 60 | var bbcode2html = function (s) { 61 | s = global$1.trim(s); 62 | var rep = function (re, str) { 63 | s = s.replace(re, str); 64 | }; 65 | rep(/\n/gi, '
'); 66 | rep(/\[b\]/gi, ''); 67 | rep(/\[\/b\]/gi, ''); 68 | rep(/\[i\]/gi, ''); 69 | rep(/\[\/i\]/gi, ''); 70 | rep(/\[u\]/gi, ''); 71 | rep(/\[\/u\]/gi, ''); 72 | rep(/\[url=([^\]]+)\](.*?)\[\/url\]/gi, '$2'); 73 | rep(/\[url\](.*?)\[\/url\]/gi, '$1'); 74 | rep(/\[img\](.*?)\[\/img\]/gi, ''); 75 | rep(/\[color=(.*?)\](.*?)\[\/color\]/gi, '$2'); 76 | rep(/\[code\](.*?)\[\/code\]/gi, '$1 '); 77 | rep(/\[quote.*?\](.*?)\[\/quote\]/gi, '$1 '); 78 | return s; 79 | }; 80 | 81 | function Plugin () { 82 | global.add('bbcode', function (editor) { 83 | editor.on('BeforeSetContent', function (e) { 84 | e.content = bbcode2html(e.content); 85 | }); 86 | editor.on('PostProcess', function (e) { 87 | if (e.set) { 88 | e.content = bbcode2html(e.content); 89 | } 90 | if (e.get) { 91 | e.content = html2bbcode(e.content); 92 | } 93 | }); 94 | }); 95 | } 96 | 97 | Plugin(); 98 | 99 | }()); 100 | -------------------------------------------------------------------------------- /public/js/tinymce/plugins/anchor/plugin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Tiny Technologies, Inc. All rights reserved. 3 | * Licensed under the LGPL or a commercial license. 4 | * For LGPL see License.txt in the project root for license information. 5 | * For commercial licenses see https://www.tiny.cloud/ 6 | * 7 | * Version: 5.3.2 (2020-06-10) 8 | */ 9 | (function () { 10 | 'use strict'; 11 | 12 | var global = tinymce.util.Tools.resolve('tinymce.PluginManager'); 13 | 14 | var isNamedAnchor = function (editor, node) { 15 | return node.tagName === 'A' && editor.dom.getAttrib(node, 'href') === ''; 16 | }; 17 | var isValidId = function (id) { 18 | return /^[A-Za-z][A-Za-z0-9\-:._]*$/.test(id); 19 | }; 20 | var getId = function (editor) { 21 | var selectedNode = editor.selection.getNode(); 22 | return isNamedAnchor(editor, selectedNode) ? selectedNode.getAttribute('id') || selectedNode.getAttribute('name') : ''; 23 | }; 24 | var insert = function (editor, id) { 25 | var selectedNode = editor.selection.getNode(); 26 | if (isNamedAnchor(editor, selectedNode)) { 27 | selectedNode.removeAttribute('name'); 28 | selectedNode.id = id; 29 | editor.undoManager.add(); 30 | } else { 31 | editor.focus(); 32 | editor.selection.collapse(true); 33 | editor.insertContent(editor.dom.createHTML('a', { id: id })); 34 | } 35 | }; 36 | 37 | var insertAnchor = function (editor, newId) { 38 | if (!isValidId(newId)) { 39 | editor.windowManager.alert('Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.'); 40 | return false; 41 | } else { 42 | insert(editor, newId); 43 | return true; 44 | } 45 | }; 46 | var open = function (editor) { 47 | var currentId = getId(editor); 48 | editor.windowManager.open({ 49 | title: 'Anchor', 50 | size: 'normal', 51 | body: { 52 | type: 'panel', 53 | items: [{ 54 | name: 'id', 55 | type: 'input', 56 | label: 'ID', 57 | placeholder: 'example' 58 | }] 59 | }, 60 | buttons: [ 61 | { 62 | type: 'cancel', 63 | name: 'cancel', 64 | text: 'Cancel' 65 | }, 66 | { 67 | type: 'submit', 68 | name: 'save', 69 | text: 'Save', 70 | primary: true 71 | } 72 | ], 73 | initialData: { id: currentId }, 74 | onSubmit: function (api) { 75 | if (insertAnchor(editor, api.getData().id)) { 76 | api.close(); 77 | } 78 | } 79 | }); 80 | }; 81 | 82 | var register = function (editor) { 83 | editor.addCommand('mceAnchor', function () { 84 | open(editor); 85 | }); 86 | }; 87 | 88 | var isNamedAnchorNode = function (node) { 89 | return !node.attr('href') && (node.attr('id') || node.attr('name')) && !node.firstChild; 90 | }; 91 | var setContentEditable = function (state) { 92 | return function (nodes) { 93 | for (var i = 0; i < nodes.length; i++) { 94 | if (isNamedAnchorNode(nodes[i])) { 95 | nodes[i].attr('contenteditable', state); 96 | } 97 | } 98 | }; 99 | }; 100 | var setup = function (editor) { 101 | editor.on('PreInit', function () { 102 | editor.parser.addNodeFilter('a', setContentEditable('false')); 103 | editor.serializer.addNodeFilter('a', setContentEditable(null)); 104 | }); 105 | }; 106 | 107 | var register$1 = function (editor) { 108 | editor.ui.registry.addToggleButton('anchor', { 109 | icon: 'bookmark', 110 | tooltip: 'Anchor', 111 | onAction: function () { 112 | return editor.execCommand('mceAnchor'); 113 | }, 114 | onSetup: function (buttonApi) { 115 | return editor.selection.selectorChangedWithUnbind('a:not([href])', buttonApi.setActive).unbind; 116 | } 117 | }); 118 | editor.ui.registry.addMenuItem('anchor', { 119 | icon: 'bookmark', 120 | text: 'Anchor...', 121 | onAction: function () { 122 | return editor.execCommand('mceAnchor'); 123 | } 124 | }); 125 | }; 126 | 127 | function Plugin () { 128 | global.add('anchor', function (editor) { 129 | setup(editor); 130 | register(editor); 131 | register$1(editor); 132 | }); 133 | } 134 | 135 | Plugin(); 136 | 137 | }()); 138 | -------------------------------------------------------------------------------- /public/js/tinymce/plugins/noneditable/plugin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Tiny Technologies, Inc. All rights reserved. 3 | * Licensed under the LGPL or a commercial license. 4 | * For LGPL see License.txt in the project root for license information. 5 | * For commercial licenses see https://www.tiny.cloud/ 6 | * 7 | * Version: 5.3.2 (2020-06-10) 8 | */ 9 | (function () { 10 | 'use strict'; 11 | 12 | var global = tinymce.util.Tools.resolve('tinymce.PluginManager'); 13 | 14 | var global$1 = tinymce.util.Tools.resolve('tinymce.util.Tools'); 15 | 16 | var getNonEditableClass = function (editor) { 17 | return editor.getParam('noneditable_noneditable_class', 'mceNonEditable'); 18 | }; 19 | var getEditableClass = function (editor) { 20 | return editor.getParam('noneditable_editable_class', 'mceEditable'); 21 | }; 22 | var getNonEditableRegExps = function (editor) { 23 | var nonEditableRegExps = editor.getParam('noneditable_regexp', []); 24 | if (nonEditableRegExps && nonEditableRegExps.constructor === RegExp) { 25 | return [nonEditableRegExps]; 26 | } else { 27 | return nonEditableRegExps; 28 | } 29 | }; 30 | 31 | var hasClass = function (checkClassName) { 32 | return function (node) { 33 | return (' ' + node.attr('class') + ' ').indexOf(checkClassName) !== -1; 34 | }; 35 | }; 36 | var replaceMatchWithSpan = function (editor, content, cls) { 37 | return function (match) { 38 | var args = arguments, index = args[args.length - 2]; 39 | var prevChar = index > 0 ? content.charAt(index - 1) : ''; 40 | if (prevChar === '"') { 41 | return match; 42 | } 43 | if (prevChar === '>') { 44 | var findStartTagIndex = content.lastIndexOf('<', index); 45 | if (findStartTagIndex !== -1) { 46 | var tagHtml = content.substring(findStartTagIndex, index); 47 | if (tagHtml.indexOf('contenteditable="false"') !== -1) { 48 | return match; 49 | } 50 | } 51 | } 52 | return '' + editor.dom.encode(typeof args[1] === 'string' ? args[1] : args[0]) + ''; 53 | }; 54 | }; 55 | var convertRegExpsToNonEditable = function (editor, nonEditableRegExps, e) { 56 | var i = nonEditableRegExps.length, content = e.content; 57 | if (e.format === 'raw') { 58 | return; 59 | } 60 | while (i--) { 61 | content = content.replace(nonEditableRegExps[i], replaceMatchWithSpan(editor, content, getNonEditableClass(editor))); 62 | } 63 | e.content = content; 64 | }; 65 | var setup = function (editor) { 66 | var editClass, nonEditClass; 67 | var contentEditableAttrName = 'contenteditable'; 68 | editClass = ' ' + global$1.trim(getEditableClass(editor)) + ' '; 69 | nonEditClass = ' ' + global$1.trim(getNonEditableClass(editor)) + ' '; 70 | var hasEditClass = hasClass(editClass); 71 | var hasNonEditClass = hasClass(nonEditClass); 72 | var nonEditableRegExps = getNonEditableRegExps(editor); 73 | editor.on('PreInit', function () { 74 | if (nonEditableRegExps.length > 0) { 75 | editor.on('BeforeSetContent', function (e) { 76 | convertRegExpsToNonEditable(editor, nonEditableRegExps, e); 77 | }); 78 | } 79 | editor.parser.addAttributeFilter('class', function (nodes) { 80 | var i = nodes.length, node; 81 | while (i--) { 82 | node = nodes[i]; 83 | if (hasEditClass(node)) { 84 | node.attr(contentEditableAttrName, 'true'); 85 | } else if (hasNonEditClass(node)) { 86 | node.attr(contentEditableAttrName, 'false'); 87 | } 88 | } 89 | }); 90 | editor.serializer.addAttributeFilter(contentEditableAttrName, function (nodes) { 91 | var i = nodes.length, node; 92 | while (i--) { 93 | node = nodes[i]; 94 | if (!hasEditClass(node) && !hasNonEditClass(node)) { 95 | continue; 96 | } 97 | if (nonEditableRegExps.length > 0 && node.attr('data-mce-content')) { 98 | node.name = '#text'; 99 | node.type = 3; 100 | node.raw = true; 101 | node.value = node.attr('data-mce-content'); 102 | } else { 103 | node.attr(contentEditableAttrName, null); 104 | } 105 | } 106 | }); 107 | }); 108 | }; 109 | 110 | function Plugin () { 111 | global.add('noneditable', function (editor) { 112 | setup(editor); 113 | }); 114 | } 115 | 116 | Plugin(); 117 | 118 | }()); 119 | -------------------------------------------------------------------------------- /controllers/usersController.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose") 2 | const User = require("./../models/user") 3 | const passport = require("passport") 4 | const multer = require("multer") 5 | const path = require("path") 6 | const bcrypt = require("bcrypt") 7 | const validator = require("express-validator") 8 | 9 | //profile photo upload setup 10 | const storage = multer.diskStorage({ 11 | destination: function (req, file, cb) { 12 | cb(null, path.join(__dirname, "..", "public", "images", "users-images")) 13 | }, 14 | filename: function (req, file, cb) { 15 | cb(null, file.fieldname + "-" + Date.now() + path.extname(file.originalname)) 16 | } 17 | }) 18 | exports.upload = multer({ storage: storage }) 19 | 20 | //util functions for passport 21 | exports.findUserByEmail = async (email) => { 22 | try { 23 | const user = await User.findOne({ email: email }) 24 | return user 25 | } catch (error) { 26 | next(error) 27 | } 28 | } 29 | 30 | exports.findUserById = async (id) => { 31 | try { 32 | const user = await User.findById(id) 33 | return user 34 | } catch (error) { 35 | next(error) 36 | } 37 | } 38 | 39 | exports.checkAuthenticated = (req, res, next) => { 40 | if (req.isAuthenticated()) { 41 | next() 42 | } else { 43 | res.redirect("/users/login") 44 | } 45 | } 46 | 47 | exports.checkNotAuthenticated = (req, res, next) => { 48 | if (req.isAuthenticated()) { 49 | res.redirect("/users") 50 | } else { 51 | next() 52 | } 53 | } 54 | 55 | //handling routes functions 56 | exports.getAllUsers = async (req, res, next) => { 57 | try { 58 | const users = await User.find() 59 | res.json(users) 60 | } catch (error) { 61 | next(error) 62 | } 63 | } 64 | 65 | exports.getUserById = async (req, res, next) => { 66 | //the object that will be rendered 67 | const input = { 68 | connectedUser: req.user, 69 | title: "profile page" 70 | } 71 | 72 | try { 73 | const id = req.params.id 74 | const user = await User.findById(id) 75 | .populate("questions") 76 | .populate({ 77 | path: "answers", 78 | populate: { path: "question" } 79 | }) 80 | .populate("topics") 81 | .populate("topicsFollowed") 82 | 83 | if (user) { 84 | input.title = user.firstName + " " + user.lastName 85 | user.registrationDate = new Date(parseInt(user.registrationDate)).toDateString() 86 | input.user = user 87 | 88 | if (input.connectedUser) { 89 | input.isConnectedUserProfile = input.connectedUser._id.toString() === user._id.toString() 90 | } 91 | 92 | //if we get to this method using this route: /users/:id/:content and not /users/:id 93 | if (req.params.content) { 94 | const { content } = req.params 95 | if (content === "questions") { 96 | input.questions = true 97 | } else if (content === "answers") { 98 | input.answers = true 99 | } else if (content === "topics") { 100 | input.topics = true 101 | } else if (content === "topics-followed") { 102 | input.topicsFollowed = true 103 | } 104 | } 105 | } 106 | res.render("profile", input) 107 | } catch (error) { 108 | if (error.message && error.message.includes("Cast to ObjectId failed for value")) { 109 | res.render("profile", input) 110 | } else { 111 | next(error) 112 | } 113 | } 114 | } 115 | 116 | exports.getLoginPage = (req, res, next) => { 117 | const input = { 118 | title: "login" 119 | } 120 | res.render("login", input) 121 | } 122 | 123 | exports.getRegistrationPage = (req, res, next) => { 124 | const input = { 125 | title: "register" 126 | } 127 | res.render("register", input) 128 | } 129 | 130 | exports.validateFormFields = () => { 131 | return [ 132 | validator 133 | .check("firstName") 134 | .notEmpty() 135 | .withMessage("firstName cannot be empty!") 136 | .isLength({ min: 2 }) 137 | .withMessage("firstName length must be at least 2 chars!"), 138 | 139 | validator 140 | .check("lastName") 141 | .notEmpty() 142 | .withMessage("lastName cannot be empty!") 143 | .isLength({ min: 2 }) 144 | .withMessage("lastName length must be at least 2 chars!"), 145 | 146 | validator.check("email").isEmail().withMessage("the email is not valid!"), 147 | 148 | validator.check("password").isLength({ min: 4 }).withMessage("password lenght must be at least 4 chars!") 149 | ] 150 | } 151 | 152 | exports.handleRegistration = async (req, res, next) => { 153 | const input = { 154 | title: "register" 155 | } 156 | try { 157 | const errors = validator.validationResult(req) 158 | 159 | if (!errors.isEmpty()) { 160 | res.render("register", { errors: errors.array() }) 161 | } else { 162 | const { firstName, lastName, email, password } = req.body 163 | const hashedPassword = await bcrypt.hash(password, 10) 164 | 165 | const newUser = await User.create({ 166 | firstName, 167 | lastName, 168 | email, 169 | password: hashedPassword, 170 | imageName: req.file ? req.file.filename : "default-user-image.jpg" 171 | }) 172 | 173 | res.render("register", { successMessage: "account successfully created!" }) 174 | } 175 | } catch (error) { 176 | next(error) 177 | } 178 | } 179 | 180 | exports.handleLogout = (req, res, next) => { 181 | req.logOut() 182 | res.redirect("/topics") 183 | } 184 | --------------------------------------------------------------------------------