├── .babelrc ├── .dockerignore ├── .editorconfig ├── .gitattributes ├── .gitignore ├── .travis.yml ├── .yo-rc.json ├── Dockerfile ├── Dockerfile.dev ├── LICENSE ├── Meanbase-Frontend.png ├── Meanbase-Sidebar.png ├── Meanbase-Themes-Page.png ├── blog ├── config ├── default.json └── production.json ├── data └── .gitignore ├── docker-compose.dev.yml ├── docker-compose.prerender.yml ├── docker-compose.yml ├── e2e └── main │ ├── main.po.js │ └── main.spec.js ├── export ├── .gitignore ├── export │ └── .gitignore └── import │ └── .gitignore ├── gulp ├── admin │ ├── bower.js │ └── imports.js ├── app │ ├── bower.js │ └── imports.js ├── build │ └── dist.js ├── document.js ├── extensions │ └── imports.js ├── test.js └── themes │ └── imports.js ├── gulpfile.js ├── karma.conf.js ├── meanbase-logo.png ├── meanbase-screenshot.png ├── meanbase.dev.env ├── nginx-prerender ├── Dockerfile ├── conf.d │ └── meanbase-nginx.template └── nginx.conf ├── nginx ├── Dockerfile ├── conf.d │ └── meanbase-nginx.template └── nginx.conf ├── package.json ├── protractor.conf.js ├── public ├── admin │ ├── bower.json │ ├── code │ │ ├── account │ │ │ ├── account.js │ │ │ ├── login │ │ │ │ ├── login.controller.js │ │ │ │ ├── login.jade │ │ │ │ └── login.styl │ │ │ └── settings │ │ │ │ ├── settings.controller.js │ │ │ │ └── settings.jade │ │ ├── analytics │ │ │ ├── analytics.controller.js │ │ │ ├── analytics.controller.spec.js │ │ │ ├── analytics.jade │ │ │ ├── analytics.js │ │ │ ├── analytics.styl │ │ │ └── google-analytics-embed-customizations.js │ │ ├── app.js │ │ ├── app.styl │ │ ├── cms │ │ │ ├── cms.controller.js │ │ │ ├── cms.controller.spec.js │ │ │ ├── cms.jade │ │ │ ├── cms.js │ │ │ └── cms.styl │ │ ├── comments │ │ │ ├── comments.controller.js │ │ │ ├── comments.controller.spec.js │ │ │ ├── comments.jade │ │ │ ├── comments.js │ │ │ └── comments.styl │ │ ├── components │ │ │ ├── camel-to-human │ │ │ │ ├── camel-to-human.filter.js │ │ │ │ └── camel-to-human.filter.spec.js │ │ │ ├── crud │ │ │ │ └── crud.service.js │ │ │ ├── date-picker │ │ │ │ └── date-picker.directive.js │ │ │ ├── dialog │ │ │ │ └── dialog.directive.js │ │ │ └── mdl │ │ │ │ └── mdl.directive.js │ │ ├── extensions │ │ │ ├── extensions.controller.js │ │ │ ├── extensions.controller.spec.js │ │ │ ├── extensions.jade │ │ │ ├── extensions.js │ │ │ └── extensions.styl │ │ ├── import │ │ │ ├── import.controller.js │ │ │ ├── import.controller.spec.js │ │ │ ├── import.jade │ │ │ ├── import.js │ │ │ └── import.styl │ │ ├── media │ │ │ ├── media.controller.js │ │ │ ├── media.controller.spec.js │ │ │ ├── media.jade │ │ │ ├── media.js │ │ │ └── media.styl │ │ ├── pages │ │ │ ├── pages.controller.js │ │ │ ├── pages.controller.spec.js │ │ │ ├── pages.jade │ │ │ ├── pages.js │ │ │ └── pages.styl │ │ ├── themes │ │ │ ├── theme.modal.controller.js │ │ │ ├── themes.controller.js │ │ │ ├── themes.controller.spec.js │ │ │ ├── themes.jade │ │ │ ├── themes.js │ │ │ └── themes.styl │ │ └── users │ │ │ ├── users.controller.js │ │ │ ├── users.controller.spec.js │ │ │ ├── users.jade │ │ │ ├── users.js │ │ │ └── users.styl │ ├── fonts │ │ ├── FontAwesome.otf │ │ ├── fontawesome-webfont.eot │ │ ├── fontawesome-webfont.svg │ │ ├── fontawesome-webfont.ttf │ │ ├── fontawesome-webfont.woff │ │ └── fontawesome-webfont.woff2 │ └── index.js ├── app │ ├── app.js │ ├── app.styl │ ├── bower.json │ ├── components │ │ ├── cms.headbar │ │ │ ├── choose-link.modal.jade │ │ │ ├── cms.headbar.controller.js │ │ │ ├── cms.headbar.jade │ │ │ ├── cms.headbar.styl │ │ │ └── editmodal.modal.jade │ │ ├── main │ │ │ ├── main.controller.js │ │ │ ├── main.controller.spec.js │ │ │ ├── main.jade │ │ │ ├── main.js │ │ │ └── main.styl │ │ ├── mb-add-menu-item │ │ │ ├── mb-add-menu-item.controller.js │ │ │ ├── mb-add-menu-item.directive.js │ │ │ ├── mb-add-menu-item.modal.jade │ │ │ └── mb-add-menu-item.service.js │ │ ├── mb-choose-icon │ │ │ ├── mb-choose-icon.directive.js │ │ │ ├── mb-choose-icon.styl │ │ │ ├── mb-edit-icon.controller.js │ │ │ ├── mb-edit-icon.modal.jade │ │ │ └── mb-edit-icon.service.js │ │ ├── mb-choose-image │ │ │ ├── mb-choose-image.directive.js │ │ │ └── mb-choose-image.styl │ │ ├── mb-choose-link │ │ │ ├── mb-choose-link.directive.js │ │ │ ├── mb-choose-link.styl │ │ │ ├── mb-edit-link.controller.js │ │ │ ├── mb-edit-link.modal.jade │ │ │ └── mb-edit-link.service.js │ │ ├── mb-dynamic-html │ │ │ ├── mb-dynamic-html.directive.js │ │ │ └── mb-dynamic-html.directive.spec.js │ │ ├── mb-edit-link │ │ │ ├── mb-edit-link.directive.js │ │ │ └── mb-edit-link.styl │ │ ├── mb-edit-menu │ │ │ ├── mb-edit-menu.controller.js │ │ │ ├── mb-edit-menu.directive.js │ │ │ ├── mb-edit-menu.modal.jade │ │ │ ├── mb-edit-menu.service.js │ │ │ └── mb-edit-menu.styl │ │ ├── mb-extension-edit │ │ │ ├── mb-extension-edit.directive.js │ │ │ ├── mb-extension-edit.modal.jade │ │ │ ├── mb-extension-edit.service.js │ │ │ └── mb-extension-edit.styl │ │ ├── mb-find-images-modal │ │ │ ├── mb-find-image.modal.jade │ │ │ ├── mb-find-image.service.js │ │ │ ├── mb-find-images-modal.directive.js │ │ │ ├── mb-find-images-modal.directive.spec.js │ │ │ └── mb-find-images-modal.styl │ │ ├── mb-grid-item │ │ │ ├── mb-grid-item.directive.js │ │ │ └── mb-grid-item.styl │ │ ├── mb-icon │ │ │ └── mb-icon.directive.js │ │ ├── mb-init-list │ │ │ └── init-list.directive.js │ │ ├── mb-link │ │ │ ├── mb-link.directive.js │ │ │ └── mb-link.styl │ │ ├── mb-list-add │ │ │ ├── mb-list-add.directive.js │ │ │ └── mb-list-add.styl │ │ ├── mb-list-area │ │ │ ├── mb-list-area-directive.js │ │ │ ├── mb-list-area.directive.spec.js │ │ │ ├── mb-list-area.jade │ │ │ └── mb-list-area.styl │ │ ├── mb-list-remove │ │ │ ├── mb-list-remove.directive.js │ │ │ └── mb-list-remove.styl │ │ ├── mb-list-selector │ │ │ ├── mb-list-selector.directive.js │ │ │ ├── mb-list-selector.jade │ │ │ ├── mb-list-selector.styl │ │ │ ├── mb-list.modal.controller.js │ │ │ └── mb-list.modal.jade │ │ ├── mb-recaptcha │ │ │ └── mb-recaptcha.directive.js │ │ ├── mb-src │ │ │ └── mb-src.directive.js │ │ ├── mb-text │ │ │ ├── mb-text.directive.js │ │ │ ├── mb-text.service.js │ │ │ └── mb-text.styl │ │ └── meanbase-editable │ │ │ ├── icons-2x.png │ │ │ ├── icons.png │ │ │ ├── meanbase-editable.directive.js │ │ │ ├── meanbase-editable.directive.spec.js │ │ │ └── meanbase-editable.directive.styl │ ├── favicon.ico │ ├── fonts │ │ ├── FontAwesome.otf │ │ ├── fontawesome-webfont.eot │ │ ├── fontawesome-webfont.svg │ │ ├── fontawesome-webfont.ttf │ │ ├── fontawesome-webfont.woff │ │ └── fontawesome-webfont.woff2 │ ├── index.js │ └── robots.txt ├── assets │ └── .gitignore ├── extensions │ ├── .gitignore │ ├── mb-search │ │ ├── extension.min.js │ │ ├── extension.min.js.map │ │ ├── index.js │ │ ├── mb-search-box-extension.html │ │ ├── screenshot.png │ │ ├── search-form.directive.js │ │ ├── search-form.html │ │ └── search-form.styl │ ├── mb-subscribe │ │ ├── extension.min.js │ │ ├── extension.min.js.map │ │ ├── index.js │ │ └── subscribe-form-extension.jade │ ├── md-bullet-point-list │ │ ├── bullet-point-list-extension.html │ │ ├── extension.min.js │ │ ├── extension.min.js.map │ │ └── index.js │ └── md-selling-point-list │ │ ├── extension.min.js │ │ ├── extension.min.js.map │ │ ├── index.js │ │ ├── selling-point-extension.html │ │ ├── selling-point-list.directive.js │ │ ├── selling-point-list.html │ │ ├── selling-point-list.styl │ │ └── toggle-type.modal.html ├── shared │ ├── api │ │ ├── api.service.js │ │ └── api.service.spec.js │ ├── auth │ │ └── auth.service.js │ ├── doubleClick │ │ ├── doubleClick.directive.js │ │ └── doubleClick.directive.spec.js │ ├── endpoints │ │ ├── endpoints.service.js │ │ └── endpoints.service.spec.js │ ├── fallback-src │ │ ├── fallback-src.directive.js │ │ └── fallback-src.directive.spec.js │ ├── feathers │ │ └── feathers.service.js │ ├── helpers │ │ ├── helpers.service.js │ │ └── helpers.service.spec.js │ ├── htmlToPlainText │ │ └── htmlToPlainText.filter.js │ ├── image-selector │ │ ├── image-selector.directive.js │ │ ├── image-selector.directive.spec.js │ │ ├── image-selector.jade │ │ └── image-selector.styl │ ├── mb-animate │ │ ├── mb-animate.directive.js │ │ └── mb-animate.directive.spec.js │ ├── missing │ │ ├── missing.controller.js │ │ ├── missing.controller.spec.js │ │ ├── missing.jade │ │ ├── missing.js │ │ └── missing.styl │ ├── mongoose-error │ │ └── mongoose-error.directive.js │ ├── ng-enter │ │ ├── ng-enter.directive.js │ │ └── ng-enter.directive.spec.js │ ├── sortable │ │ ├── sortable.directive.js │ │ ├── sortable.directive.spec.js │ │ ├── sortable.jade │ │ └── sortable.styl │ ├── taglist │ │ ├── taglist.directive.js │ │ ├── taglist.directive.spec.js │ │ ├── taglist.jade │ │ └── taglist.styl │ └── validate │ │ ├── validate.directive.js │ │ ├── validate.directive.spec.js │ │ ├── validate.jade │ │ └── validate.styl └── themes │ ├── .gitignore │ └── meanbase-demo │ ├── LICENSE │ ├── README.md │ ├── css │ └── landing-page.css │ ├── img │ ├── banner-bg.jpg │ ├── dog.png │ ├── intro-bg.jpg │ ├── ipad.png │ └── phones.png │ ├── index.js │ ├── screenshot.png │ ├── templates │ ├── archive │ │ ├── archive-screenshot.png │ │ ├── archive-template.html │ │ ├── archive.controller.js │ │ └── archive.styl │ ├── blog │ │ ├── blog-screenshot.png │ │ ├── blog-template.html │ │ ├── blog.styl │ │ └── clean-blog.styl │ └── home │ │ ├── home-screenshot.png │ │ └── home-template.html │ ├── theme.json │ ├── theme.min.js │ └── theme.min.js.map ├── readme.md ├── setup.sh ├── src ├── app.js ├── components │ ├── compile-index │ │ └── index.js │ ├── patterns │ │ └── index.js │ ├── search-folders │ │ └── index.js │ ├── seed │ │ ├── index.js │ │ └── seed │ │ │ ├── comments.js │ │ │ ├── extensions.js │ │ │ ├── menus.js │ │ │ ├── pages.js │ │ │ ├── roles.js │ │ │ ├── themes.js │ │ │ └── user.js │ ├── settings │ │ └── index.js │ └── utility │ │ └── index.js ├── hooks │ ├── allow-upsert.js │ ├── attach-permissions.js │ ├── delete-custom-data.js │ ├── filter-by-permission-or-restrict.js │ ├── has-permission-or-restrict.js │ ├── has-permission.js │ ├── if-password-then-hash.js │ ├── index.js │ ├── is-enabled.js │ ├── is-target-enabled.js │ ├── owner-or-restrict-changes.js │ ├── permission-or-restrict-changes.js │ ├── permit-change-password.js │ ├── populate-or-restrict.js │ ├── recaptcha.js │ ├── remove-from-disk.js │ ├── send-verification-email.js │ ├── unzip.js │ ├── update-by-permission.js │ └── verify-or-restrict.js ├── index.js ├── middleware │ ├── index.js │ ├── logger.js │ └── not-found-handler.js ├── routes.js └── services │ ├── authentication │ ├── hooks │ │ └── index.js │ └── index.js │ ├── ban │ ├── ban-model.js │ ├── hooks │ │ └── index.js │ └── index.js │ ├── comments │ ├── comments-model.js │ ├── hooks │ │ ├── are-comments-permitted.js │ │ ├── google-recaptcha.js │ │ ├── index.js │ │ ├── is-approved.js │ │ └── prepend-slash.js │ └── index.js │ ├── custom │ ├── custom-model.js │ ├── hooks │ │ └── index.js │ └── index.js │ ├── email │ ├── hooks │ │ └── index.js │ └── index.js │ ├── extension-uploads │ ├── extension-uploads.service.js │ ├── hooks │ │ ├── examine-extension.js │ │ └── index.js │ └── index.js │ ├── extensions │ ├── extensions-model.js │ ├── hooks │ │ └── index.js │ └── index.js │ ├── image-uploads │ ├── hooks │ │ ├── index.js │ │ └── resize.js │ ├── image-uploads.service.js │ └── index.js │ ├── images │ ├── hooks │ │ └── index.js │ ├── images-model.js │ └── index.js │ ├── import-export │ ├── hooks │ │ ├── index.js │ │ └── unzip.js │ └── index.js │ ├── index.js │ ├── menus │ ├── hooks │ │ ├── array-to-object.js │ │ ├── index.js │ │ └── object-to-array.js │ ├── index.js │ └── menus-model.js │ ├── pages │ ├── hooks │ │ ├── convert-for-incoming.js │ │ ├── convert-for-outgoing.js │ │ ├── index.js │ │ ├── mirror-menus.js │ │ └── notify-subscribers.js │ ├── index.js │ └── pages-model.js │ ├── roles │ ├── hooks │ │ └── index.js │ ├── index.js │ └── roles-model.js │ ├── settings │ ├── hooks │ │ ├── index.js │ │ └── recompile-index.js │ ├── index.js │ └── settings-model.js │ ├── shared-content │ ├── hooks │ │ └── index.js │ ├── index.js │ └── shared-content-model.js │ ├── staging │ ├── hooks │ │ └── index.js │ ├── index.js │ └── staging-model.js │ ├── subscribe │ ├── hooks │ │ └── index.js │ └── index.js │ ├── theme-uploads │ ├── hooks │ │ ├── examine-theme.js │ │ └── index.js │ ├── index.js │ └── theme-uploads.service.js │ ├── themes │ ├── hooks │ │ ├── index.js │ │ └── remove-from-disk.js │ ├── index.js │ └── themes-model.js │ ├── user │ ├── hooks │ │ └── index.js │ ├── index.js │ └── user-model.js │ └── wordpress-import │ ├── hooks │ ├── convert-xml-to-json.js │ ├── import-wordpress.js │ └── index.js │ ├── index.js │ ├── unzip.js │ └── wordpress-import.service.js ├── test ├── app.test.js └── services │ ├── ban │ └── index.test.js │ ├── comments │ └── index.test.js │ ├── custom │ └── index.test.js │ ├── email │ └── index.test.js │ ├── extension │ └── index.test.js │ ├── extensions │ └── index.test.js │ ├── images │ └── index.test.js │ ├── import-export │ └── index.test.js │ ├── menus │ └── index.test.js │ ├── pages │ └── index.test.js │ ├── roles │ └── index.test.js │ ├── settings │ └── index.test.js │ ├── shared-content │ └── index.test.js │ ├── subscribe │ └── index.test.js │ ├── themes │ └── index.test.js │ └── user │ └── index.test.js ├── update.sh ├── views ├── admin.html └── index.html └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2017"], 3 | "plugins": ["transform-async-to-generator", "angularjs-annotate"] 4 | } 5 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | # Ignore everything 2 | * 3 | 4 | # But not these files... 5 | !.gitignore 6 | !.dockerignore 7 | !package.json 8 | !gulpfile.js 9 | !webpack.config.js 10 | !dist 11 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | 8 | [*] 9 | 10 | # Change these settings to your own preference 11 | indent_style = space 12 | indent_size = 2 13 | 14 | # We recommend you to keep these unchanged 15 | end_of_line = lf 16 | charset = utf-8 17 | trim_trailing_whitespace = true 18 | insert_final_newline = true 19 | 20 | [*.md] 21 | trim_trailing_whitespace = false 22 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | public/admin/bower_components 4 | public/admin/*.png 5 | public/app/*.png 6 | public/app/*.svg 7 | public/admin/public/fonts 8 | public/app/public/fonts 9 | public/app/bower_components 10 | public/admin/bundle.js 11 | public/admin/index.html 12 | public/admin/bundle.js.map 13 | public/admin/bower.js 14 | public/app/bower.js 15 | public/app/index.html 16 | public/app/bundle.js 17 | public/app/bundle.js.map 18 | public/themes/*/theme.min.js 19 | public/themes/*/theme.min.js.map 20 | public/old-app/ 21 | public/old-app/** 22 | server/logs/debug.log 23 | .vscode 24 | .tmp 25 | old-server 26 | .idea 27 | bower_components 28 | documentation 29 | dist 30 | !gulp/build/dist.js 31 | /server/config/local.env.js 32 | __MACOSX 33 | code-documentation 34 | meanbase.env 35 | environment.env 36 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '0.10' 4 | - '0.11' 5 | before_script: 6 | - npm install -g bower grunt-cli 7 | - bower install 8 | services: mongodb -------------------------------------------------------------------------------- /.yo-rc.json: -------------------------------------------------------------------------------- 1 | { 2 | "generator-angular-fullstack": { 3 | "insertRoutes": true, 4 | "registerRoutesFile": "server/routes.js", 5 | "routesNeedle": "// Insert routes below", 6 | "routesBase": "/api/", 7 | "pluralizeRoutes": true, 8 | "insertSockets": true, 9 | "registerSocketsFile": "server/config/socketio.js", 10 | "socketsNeedle": "// Insert sockets below", 11 | "filters": { 12 | "js": true, 13 | "jade": true, 14 | "stylus": true, 15 | "uirouter": true, 16 | "bootstrap": true, 17 | "uibootstrap": true, 18 | "mongoose": true, 19 | "auth": true, 20 | "oauth": true, 21 | "googleAuth": true, 22 | "facebookAuth": true, 23 | "twitterAuth": true 24 | } 25 | }, 26 | "generator-ng-component": { 27 | "routeDirectory": "client/app/", 28 | "directiveDirectory": "client/app/", 29 | "filterDirectory": "client/app/", 30 | "serviceDirectory": "client/app/", 31 | "basePath": "client", 32 | "moduleName": "", 33 | "filters": [ 34 | "uirouter" 35 | ], 36 | "extensions": [ 37 | "js", 38 | "jade", 39 | "styl" 40 | ], 41 | "directiveSimpleTemplates": "", 42 | "directiveComplexTemplates": "", 43 | "filterTemplates": "", 44 | "serviceTemplates": "", 45 | "factoryTemplates": "", 46 | "controllerTemplates": "", 47 | "decoratorTemplates": "", 48 | "providerTemplates": "", 49 | "routeTemplates": "" 50 | } 51 | } -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | ############################################################ 2 | # Dockerfile to build Meanbase 3 | # Based on Ubuntu 4 | ############################################################ 5 | 6 | FROM codingfriend/meanbase 7 | 8 | RUN npm install --global npm-install-que 9 | 10 | ################## ESTABLISH DIRECTORIES ###################### 11 | RUN rm -rf /var/www/ 12 | WORKDIR /var/www/ 13 | # COPY dist/package.json /var/www/ 14 | ENV NODE_ENV=production 15 | # RUN npm-install-que --production 16 | # RUN npm config set jobs 1 17 | # RUN npm install feathers express passport prerender-node 18 | # RUN npm install 19 | COPY dist/ /var/www/ 20 | ################## END DIRECTORIES ###################### 21 | 22 | # Expose the default port 23 | EXPOSE 8080 24 | VOLUME /var/www 25 | 26 | # CMD ["pm2", "start", "src", "--no-daemon"] 27 | CMD ["pm2", "start", "src", "--no-daemon"] 28 | -------------------------------------------------------------------------------- /Dockerfile.dev: -------------------------------------------------------------------------------- 1 | ############################################################ 2 | # Dockerfile to build Meanbase 3 | # Based on Ubuntu 4 | ############################################################ 5 | 6 | FROM codingfriend/meanbase 7 | 8 | 9 | ################## ESTABLISH DIRECTORIES ###################### 10 | RUN rm -rf /var/www/ 11 | WORKDIR /var/www/ 12 | COPY ./package.json /var/www/ 13 | RUN npm install 14 | COPY . /var/www/ 15 | ################## END DIRECTORIES ###################### 16 | 17 | # Expose the default port 18 | EXPOSE 3030 19 | VOLUME /var/www 20 | 21 | CMD ["npm", "start"] 22 | -------------------------------------------------------------------------------- /Meanbase-Frontend.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingfriend1/meanbase/6639e49f9c7eae7cdf5b4a1c7ff17f8ed9c0215f/Meanbase-Frontend.png -------------------------------------------------------------------------------- /Meanbase-Sidebar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingfriend1/meanbase/6639e49f9c7eae7cdf5b4a1c7ff17f8ed9c0215f/Meanbase-Sidebar.png -------------------------------------------------------------------------------- /Meanbase-Themes-Page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingfriend1/meanbase/6639e49f9c7eae7cdf5b4a1c7ff17f8ed9c0215f/Meanbase-Themes-Page.png -------------------------------------------------------------------------------- /blog: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingfriend1/meanbase/6639e49f9c7eae7cdf5b4a1c7ff17f8ed9c0215f/blog -------------------------------------------------------------------------------- /data/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore everything 2 | * 3 | 4 | # But not these files... 5 | !.gitignore 6 | -------------------------------------------------------------------------------- /docker-compose.dev.yml: -------------------------------------------------------------------------------- 1 | db: 2 | image: mongo 3 | ports: 4 | - "27017" 5 | - "37017" 6 | command: --smallfiles 7 | web: 8 | build: . 9 | dockerfile: Dockerfile.dev 10 | ports: 11 | - "3030:3030" 12 | env_file: 13 | - meanbase.dev.env 14 | links: 15 | - db:db 16 | volumes: 17 | - .:/var/www 18 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "2.1" 2 | services: 3 | db: 4 | image: mongo 5 | expose: 6 | - "27017" 7 | - "37017" 8 | command: --smallfiles 9 | restart: unless-stopped 10 | web: 11 | build: . 12 | expose: 13 | - "80" 14 | env_file: 15 | - meanbase.env 16 | restart: unless-stopped 17 | links: 18 | - db:db 19 | nginx: 20 | restart: unless-stopped 21 | build: nginx 22 | links: 23 | - web:web 24 | volumes_from: 25 | - web 26 | ports: 27 | - "80:80" 28 | - "443:443" 29 | env_file: 30 | - meanbase.env 31 | command: /bin/bash -c "envsubst '$${PRERENDER_TOKEN}' < /etc/nginx/conf.d/meanbase-nginx.template > /etc/nginx/conf.d/meanbase-nginx.conf && nginx -g 'daemon off;'" 32 | volumes: 33 | - certs:/etc/nginx/certs 34 | - /tmp/letsencrypt/www:/tmp/letsencrypt/www 35 | letsencrypt: 36 | restart: unless-stopped 37 | container_name: letsencrypt 38 | image: gordonchan/auto-letsencrypt 39 | env_file: 40 | - meanbase.env 41 | environment: 42 | - SERVER_CONTAINER=letsencrypt 43 | - WEBROOT_PATH=/tmp/letsencrypt/www 44 | - CERTS_PATH=/etc/nginx/certs 45 | - CHECK_FREQ=7 46 | - LE_RENEW_HOOK=docker kill -s HUP @CONTAINER_NAME@ 47 | volumes: 48 | - /var/log/letsencrypt/:/var/log/letsencrypt 49 | - /var/run/docker.sock:/var/run/docker.sock 50 | - /etc/letsencrypt:/etc/letsencrypt 51 | - /var/lib/letsencrypt:/var/lib/letsencrypt 52 | - /tmp/letsencrypt/www:/tmp/letsencrypt/www 53 | - certs:/etc/nginx/certs 54 | links: 55 | - nginx 56 | volumes: 57 | certs: 58 | -------------------------------------------------------------------------------- /e2e/main/main.po.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file uses the Page Object pattern to define the main page for tests 3 | * https://docs.google.com/presentation/d/1B6manhG0zEXkC-H-tPo2vwU06JhL8w9-XCF9oehXzAQ 4 | */ 5 | 6 | 'use strict'; 7 | 8 | var MainPage = function() { 9 | this.heroEl = element(by.css('.hero-unit')); 10 | this.h1El = this.heroEl.element(by.css('h1')); 11 | this.imgEl = this.heroEl.element(by.css('img')); 12 | }; 13 | 14 | module.exports = new MainPage(); 15 | 16 | -------------------------------------------------------------------------------- /e2e/main/main.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Main View', function() { 4 | var page; 5 | 6 | beforeEach(function() { 7 | browser.get('/'); 8 | page = require('./main.po'); 9 | }); 10 | 11 | it('should include jumbotron with correct data', function() { 12 | expect(page.h1El.getText()).toBe('\'Allo, \'Allo!'); 13 | expect(page.imgEl.getAttribute('src')).toMatch(/assets\/images\/yeoman.png$/); 14 | expect(page.imgEl.getAttribute('alt')).toBe('I\'m Yeoman'); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /export/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore everything 2 | * 3 | # But not these files... 4 | !.gitignore 5 | !export/ 6 | !import/ 7 | -------------------------------------------------------------------------------- /export/export/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore everything 2 | * 3 | # But not these files... 4 | !.gitignore 5 | -------------------------------------------------------------------------------- /export/import/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore everything 2 | * 3 | # But not these files... 4 | !.gitignore 5 | -------------------------------------------------------------------------------- /gulp/admin/bower.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = function(gulp, plugins, folders, config) { 3 | gulp.task('copy-fonts-admin', function() { 4 | return plugins.mergeStream( 5 | gulp.src(folders.bower + '/font-awesome/fonts/**') 6 | .pipe(plugins.chmod(755)) 7 | .pipe(gulp.dest(folders.root + '/fonts/')) 8 | ) 9 | }) 10 | 11 | gulp.task('create-bower-admin', function() { 12 | var js = gulp.src(plugins.mainBowerFiles('**/*.js', { 13 | paths: { 14 | bowerDirectory: folders.root + '/bower_components', 15 | bowerJson: folders.root + '/bower.json' 16 | } 17 | })) 18 | 19 | var css = gulp.src(plugins.mainBowerFiles('**/*.css', { 20 | paths: { 21 | bowerDirectory: folders.root + '/bower_components', 22 | bowerJson: folders.root + '/bower.json' 23 | } 24 | })) 25 | .pipe(plugins.cssToJs()) 26 | 27 | return plugins.es.merge(css, js) 28 | .pipe(plugins.concat('bower.js')) 29 | .pipe(plugins.uglify()) 30 | .pipe(gulp.dest(folders.root)) 31 | }) 32 | } 33 | -------------------------------------------------------------------------------- /gulp/app/bower.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = function(gulp, plugins, folders, config) { 3 | gulp.task('copy-fonts-app', function() { 4 | return plugins.mergeStream( 5 | gulp.src(folders.bower + '/font-awesome/fonts/**') 6 | .pipe(plugins.chmod(755)) 7 | .pipe(gulp.dest(folders.root + '/fonts/')) 8 | ) 9 | }) 10 | 11 | gulp.task('create-bower-app', function() { 12 | var js = gulp.src(plugins.mainBowerFiles('**/*.js', { 13 | paths: { 14 | bowerDirectory: folders.root + '/bower_components', 15 | bowerJson: folders.root + '/bower.json' 16 | } 17 | })) 18 | .pipe(plugins.debug('js')) 19 | 20 | var css = gulp.src(plugins.mainBowerFiles('**/*.css', { 21 | paths: { 22 | bowerDirectory: folders.root + '/bower_components', 23 | bowerJson: folders.root + '/bower.json' 24 | } 25 | })) 26 | .pipe(plugins.debug('css')) 27 | .pipe(plugins.cssToJs()) 28 | 29 | return plugins.es.merge(css, js) 30 | .pipe(plugins.concat('bower.js')) 31 | // .pipe(plugins.uglify()) 32 | .pipe(gulp.dest(folders.root)) 33 | }) 34 | } 35 | -------------------------------------------------------------------------------- /gulp/document.js: -------------------------------------------------------------------------------- 1 | module.exports = function (gulp, plugins) { 2 | gulp.task('clear-docs', function () { 3 | return plugins.del('documentation/**'); 4 | }); 5 | 6 | gulp.task('document', ['clear-docs'], function(done) { 7 | // gulp.src([ 8 | // 'client/{app, components}/**/*.js', 9 | // 'README.md', 10 | // 'server/**.js' 11 | // ]) 12 | plugins.run("docker -i client/ --exclude bower_components,*spec.js,*.styl,*.jade,extensions,assets,themes,tasks,e2e,dist,.tmp,Gruntfile.js,gulpfile.js,karma.conf.js -o documentation -u -n --ignore_hidden").exec('', done); 13 | }); 14 | }; -------------------------------------------------------------------------------- /gulp/test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @overview Handles unit tests 3 | * gulp test 4 | * Runs jasmine karma unit tests client side 5 | * @author Jon Paul Miles 6 | */ 7 | 8 | var karma = require('karma').server; 9 | 10 | module.exports = function (gulp, plugins, config) { 11 | // Unit Tests 12 | gulp.task('karma', function() { 13 | return gulp.src('../karma.conf.js') 14 | .pipe(plugins.inject(gulp.src(plugins.mainBowerFiles('**/*.js'), {read: false}), { 15 | starttag: 'files: [', 16 | endtag: "'client/bower_components/angular-mocks/angular-mocks.js'", 17 | addRootSlash: false, 18 | transform: function (filepath, file, i, length) { 19 | return '"' + filepath + '",';; 20 | } 21 | })) 22 | .pipe(gulp.dest('./')); 23 | }); 24 | 25 | gulp.task('test', ['karma'], function (done) { 26 | karma.start({ 27 | configFile: __dirname + '/../karma.conf.js' 28 | }, done); 29 | }); 30 | }; -------------------------------------------------------------------------------- /meanbase-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingfriend1/meanbase/6639e49f9c7eae7cdf5b4a1c7ff17f8ed9c0215f/meanbase-logo.png -------------------------------------------------------------------------------- /meanbase-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingfriend1/meanbase/6639e49f9c7eae7cdf5b4a1c7ff17f8ed9c0215f/meanbase-screenshot.png -------------------------------------------------------------------------------- /meanbase.dev.env: -------------------------------------------------------------------------------- 1 | NODE_ENV=developement 2 | DATABASE_URL=mongodb://db/meanbase-dev 3 | DATABASE_NAME=meanbase-dev 4 | RESET_SEED=false 5 | SEED=true 6 | 7 | # DOMAIN=your-domain.com 8 | # EMAIL_USER=your-gmail-username 9 | # EMAIL_PASS=your-gmail-password 10 | # EMAIL=your-gmail-email 11 | # MAILGUN_API_KEY=api-key 12 | # MAILGUN_DOMAIN=your-domain-name.com 13 | # MAILGUN_SUBSCRIPTION_EMAIL=the-mailing-list-email@your-service.com 14 | # ACCOUNT_EMAIL=the-from-email-for-your-mailing-list@your-service.com 15 | -------------------------------------------------------------------------------- /nginx-prerender/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nginx 2 | 3 | RUN rm -f /etc/nginx/nginx.conf 4 | COPY ./nginx.conf /etc/nginx/ 5 | 6 | RUN rm -rf /etc/nginx/conf.d/* 7 | WORKDIR /etc/nginx/conf.d/ 8 | COPY ./conf.d/ /etc/nginx/conf.d/ 9 | 10 | RUN ln -sf /dev/stdout /var/log/nginx/access.log 11 | RUN ln -sf /dev/stderr /var/log/nginx/error.log 12 | -------------------------------------------------------------------------------- /nginx-prerender/nginx.conf: -------------------------------------------------------------------------------- 1 | user nginx; 2 | worker_processes 1; 3 | 4 | error_log /var/log/nginx/error.log warn; 5 | pid /var/run/nginx.pid; 6 | 7 | 8 | events { 9 | worker_connections 1024; 10 | } 11 | 12 | http { 13 | include /etc/nginx/mime.types; 14 | default_type application/octet-stream; 15 | 16 | log_format main '$remote_addr - $remote_user [$time_local] "$request" ' 17 | '$status $body_bytes_sent "$http_referer" ' 18 | '"$http_user_agent" "$http_x_forwarded_for"'; 19 | 20 | access_log /var/log/nginx/access.log main; 21 | 22 | sendfile on; 23 | #tcp_nopush on; 24 | 25 | keepalive_timeout 65; 26 | 27 | gzip on; 28 | gzip_disable "msie6"; 29 | 30 | client_max_body_size 200M; 31 | 32 | gzip_vary on; 33 | gzip_proxied any; 34 | gzip_comp_level 6; 35 | gzip_buffers 16 8k; 36 | gzip_http_version 1.1; 37 | gzip_min_length 1000; 38 | gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript application/vnd.ms-fontobject application/x-font-ttf font/opentype image/svg+xml image/x-icon; 39 | 40 | include /etc/nginx/conf.d/*.conf; 41 | } 42 | -------------------------------------------------------------------------------- /nginx/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nginx 2 | 3 | RUN rm -f /etc/nginx/nginx.conf 4 | COPY ./nginx.conf /etc/nginx/ 5 | 6 | RUN rm -rf /etc/nginx/conf.d/* 7 | WORKDIR /etc/nginx/conf.d/ 8 | COPY ./conf.d/ /etc/nginx/conf.d/ 9 | 10 | RUN ln -sf /dev/stdout /var/log/nginx/access.log 11 | RUN ln -sf /dev/stderr /var/log/nginx/error.log 12 | -------------------------------------------------------------------------------- /nginx/nginx.conf: -------------------------------------------------------------------------------- 1 | user nginx; 2 | worker_processes 1; 3 | 4 | error_log /var/log/nginx/error.log warn; 5 | pid /var/run/nginx.pid; 6 | 7 | 8 | events { 9 | worker_connections 1024; 10 | } 11 | 12 | http { 13 | include /etc/nginx/mime.types; 14 | default_type application/octet-stream; 15 | 16 | log_format main '$remote_addr - $remote_user [$time_local] "$request" ' 17 | '$status $body_bytes_sent "$http_referer" ' 18 | '"$http_user_agent" "$http_x_forwarded_for"'; 19 | 20 | access_log /var/log/nginx/access.log main; 21 | 22 | sendfile on; 23 | #tcp_nopush on; 24 | 25 | keepalive_timeout 65; 26 | 27 | gzip on; 28 | gzip_disable "msie6"; 29 | 30 | client_max_body_size 200M; 31 | 32 | gzip_vary on; 33 | gzip_proxied any; 34 | gzip_comp_level 6; 35 | gzip_buffers 16 8k; 36 | gzip_http_version 1.1; 37 | gzip_min_length 1000; 38 | gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript application/vnd.ms-fontobject application/x-font-ttf font/opentype image/svg+xml image/x-icon; 39 | 40 | include /etc/nginx/conf.d/*.conf; 41 | } 42 | -------------------------------------------------------------------------------- /public/admin/code/account/account.js: -------------------------------------------------------------------------------- 1 | angular.module('meanbaseApp') 2 | .config(function ($stateProvider) { 3 | $stateProvider 4 | .state('cms.account', { 5 | url: '/account', 6 | templateUrl: require('./login/login.jade'), 7 | controller: 'LoginCtrl', 8 | icon: 'assignment_ind' 9 | }) 10 | .state('cms.accountVerify', { 11 | url: '/account/:action/:token', 12 | templateUrl: require('./login/login.jade'), 13 | controller: 'LoginCtrl', 14 | hide: true, 15 | }) 16 | .state('cms.my-settings', { 17 | url: '/my-settings', 18 | templateUrl: require('./settings/settings.jade'), 19 | controller: 'SettingsCtrl', 20 | authenticate: true, 21 | icon: 'verified_user' 22 | }) 23 | }); 24 | -------------------------------------------------------------------------------- /public/admin/code/account/login/login.styl: -------------------------------------------------------------------------------- 1 | // Social buttons 2 | // -------------------------------------------------- 3 | 4 | .btn-facebook 5 | color: #fff; 6 | background-color: #3B5998; 7 | border-color: #133783; 8 | 9 | .btn-twitter 10 | color: #fff; 11 | background-color: #2daddc; 12 | border-color: #0271bf; 13 | 14 | .btn-google-plus 15 | color: #fff; 16 | background-color: #dd4b39; 17 | border-color: #c53727; 18 | 19 | .btn-github 20 | color: #fff; 21 | background-color: #fafafa; 22 | border-color: #ccc; 23 | 24 | .margin-center 25 | margin 2em auto 26 | 27 | .mdl-color--primary 28 | background-color #34495e !important 29 | color #ECF0F1 30 | 31 | #meanbase-cms 32 | .mdl-card__supporting-text 33 | margin auto 34 | 35 | .btn-inverse:not(.btn-link):not(.btn-flat) 36 | background-color #34495e 37 | color #ECF0F1 38 | -------------------------------------------------------------------------------- /public/admin/code/account/settings/settings.controller.js: -------------------------------------------------------------------------------- 1 | angular.module('meanbaseApp') 2 | .controller('SettingsCtrl', function ($scope, Auth, toastr) { 3 | $scope.errors = {}; 4 | 5 | $scope.changePassword = function(form) { 6 | $scope.submitted = true; 7 | if(form.$valid) { 8 | Auth.changePassword( $scope.user.oldPassword, $scope.user.newPassword ) 9 | .then( function() { 10 | toastr.success('Successfully changed password'); 11 | $scope.message = 'Password successfully changed.'; 12 | }) 13 | .catch( function() { 14 | form.password.$setValidity('mongoose', false); 15 | $scope.errors.other = 'Incorrect password'; 16 | $scope.message = ''; 17 | }); 18 | } 19 | }; 20 | }); 21 | -------------------------------------------------------------------------------- /public/admin/code/account/settings/settings.jade: -------------------------------------------------------------------------------- 1 | //- div(ng-include='"components/navbar/navbar.html"') 2 | //- include ../../components/navbar/navbar.jade 3 | .container 4 | .row 5 | .col-sm-4 6 | .mdl-card.mdl-shadow--6dp.margin-center 7 | .mdl-card__supporting-text.no-padding 8 | h3 Change Password 9 | form.form(name='form', ng-submit='changePassword(form)', novalidate='') 10 | .form-group 11 | label Current Password 12 | input.form-control(type='password', name='password', ng-model='user.oldPassword', mongoose-error='' autofocus) 13 | p.help-block(ng-show='form.password.$error.mongoose') 14 | | {{ errors.other }} 15 | .form-group 16 | label New Password 17 | input.form-control(type='password', name='newPassword', ng-model='user.newPassword', ng-minlength='3', required='') 18 | p.help-block(ng-show='(form.newPassword.$error.minlength || form.newPassword.$error.required) && (form.newPassword.$dirty || submitted)') 19 | | Password must be at least 3 characters. 20 | 21 | p.help-block {{ message }} 22 | 23 | button.btn.btn-lg.btn-primary.btn-block.float-right(type='submit') Save changes 24 | -------------------------------------------------------------------------------- /public/admin/code/analytics/analytics.controller.spec.js: -------------------------------------------------------------------------------- 1 | describe('Controller: AnalyticsCtrl', function () { 2 | 3 | // load the controller's module 4 | beforeEach(module('meanbaseApp')); 5 | 6 | var AnalyticsCtrl, scope; 7 | 8 | // Initialize the controller and a mock scope 9 | beforeEach(inject(function ($controller, $rootScope) { 10 | scope = $rootScope.$new(); 11 | AnalyticsCtrl = $controller('AnalyticsCtrl', { 12 | $scope: scope 13 | }); 14 | })); 15 | 16 | it('should ...', function () { 17 | expect(1).toEqual(1); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /public/admin/code/analytics/analytics.js: -------------------------------------------------------------------------------- 1 | angular.module('meanbaseApp') 2 | .config(function ($stateProvider) { 3 | $stateProvider 4 | .state('cms.analytics', { 5 | url: '/analytics', 6 | templateUrl: require('./analytics.jade'), 7 | controller: 'AnalyticsCtrl', 8 | hasPermission: "viewAnalytics", 9 | icon: 'show_chart' 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /public/admin/code/analytics/analytics.styl: -------------------------------------------------------------------------------- 1 | #meanbase-analytics 2 | padding-top 1em 3 | 4 | button 5 | margin-top 0.5em 6 | -------------------------------------------------------------------------------- /public/admin/code/analytics/google-analytics-embed-customizations.js: -------------------------------------------------------------------------------- 1 | // (function(w,d,s,g,js,fjs){ 2 | // g=w.gapi||(w.gapi={});g.analytics={q:[],ready:function(cb){this.q.push(cb)}}; 3 | // js=d.createElement(s);fjs=d.getElementsByTagName(s)[0]; 4 | // js.src='https://apis.google.com/js/platform.js'; 5 | // fjs.parentNode.insertBefore(js,fjs);js.onload=function(){g.load('analytics')}; 6 | // }(window,document,'script')); 7 | // 8 | // gapi.analytics.ready(function() { 9 | // // Step 3: Authorize the user. 10 | // var CLIENT_ID = ''; 11 | // gapi.analytics.auth.authorize({ 12 | // container: 'auth-button', 13 | // clientid: CLIENT_ID 14 | // }); 15 | // // Step 4: Create the view selector. 16 | // var viewSelector = new gapi.analytics.ViewSelector({ 17 | // container: 'view-selector' 18 | // }); 19 | // // Step 5: Create the timeline chart. 20 | // var timeline = new gapi.analytics.googleCharts.DataChart({ 21 | // reportType: 'ga', 22 | // query: { 23 | // 'dimensions': 'ga:date', 24 | // 'metrics': 'ga:sessions', 25 | // 'start-date': '30daysAgo', 26 | // 'end-date': 'yesterday', 27 | // }, 28 | // chart: { 29 | // type: 'LINE', 30 | // container: 'timeline' 31 | // } 32 | // }); 33 | // 34 | // // Step 6: Hook up the components to work together. 35 | // gapi.analytics.auth.on('success', function(response) { 36 | // console.log("success", response); 37 | // viewSelector.execute(); 38 | // }); 39 | // viewSelector.on('change', function(ids) { 40 | // var newIds = { 41 | // query: { 42 | // ids: ids 43 | // } 44 | // } 45 | // timeline.set(newIds).execute(); 46 | // }); 47 | // }); 48 | -------------------------------------------------------------------------------- /public/admin/code/cms/cms.controller.spec.js: -------------------------------------------------------------------------------- 1 | describe('Controller: CmsCtrl', function () { 2 | 3 | // load the controller's module 4 | beforeEach(module('meanbaseApp')); 5 | 6 | var CmsCtrl, scope; 7 | 8 | // Initialize the controller and a mock scope 9 | beforeEach(inject(function ($controller, $rootScope) { 10 | scope = $rootScope.$new(); 11 | CmsCtrl = $controller('CmsCtrl', { 12 | $scope: scope 13 | }); 14 | })); 15 | 16 | it('should ...', function () { 17 | expect('app').toEqual('test'); 18 | expect(1).toEqual(1); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /public/admin/code/cms/cms.js: -------------------------------------------------------------------------------- 1 | // ### Parent route for backend. 2 | // - All routes for the admin interface have cms/ as their prefix 3 | angular.module('meanbaseApp') 4 | .config(function ($stateProvider) { 5 | $stateProvider 6 | .state('cms', { 7 | url: '/cms', 8 | templateUrl: require('./cms.jade'), 9 | controller: 'cmsCtrl', 10 | controllerAs: 'cms', 11 | authenticate: true 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /public/admin/code/comments/comments.controller.spec.js: -------------------------------------------------------------------------------- 1 | describe('Controller: CommentsCtrl', function () { 2 | 3 | // load the controller's module 4 | beforeEach(module('meanbaseApp')); 5 | 6 | var CommentsCtrl, scope; 7 | 8 | // Initialize the controller and a mock scope 9 | beforeEach(inject(function ($controller, $rootScope) { 10 | scope = $rootScope.$new(); 11 | CommentsCtrl = $controller('CommentsCtrl', { 12 | $scope: scope 13 | }); 14 | })); 15 | 16 | it('should ...', function () { 17 | expect(1).toEqual(1); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /public/admin/code/comments/comments.js: -------------------------------------------------------------------------------- 1 | angular.module('meanbaseApp') 2 | .config(function ($stateProvider) { 3 | $stateProvider 4 | .state('cms.comments', { 5 | url: '/comments', 6 | templateUrl: require('./comments.jade'), 7 | controller: 'CommentsCtrl', 8 | hasPermission: 'moderateComments', 9 | icon: 'comment' 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /public/admin/code/comments/comments.styl: -------------------------------------------------------------------------------- 1 | #meanbase-cms 2 | .mdl-list__item--infinite-line 3 | height auto !important 4 | .mdl-list__item-primary-content.banned 5 | color: #ccc 6 | i 7 | background-color: #ccc 8 | .mdl-list__item-text-body 9 | color: #ccc 10 | .mdl-list__item-primary-content 11 | height auto !important 12 | .mdl-list__item-text-body 13 | height auto !important 14 | padding 0.5em 15 | display block 16 | .no-padding 17 | padding 0 !important 18 | .all-visible 19 | background-color white 20 | color #34495E 21 | i 22 | color #34495E 23 | .margin-top 24 | margin-top 1em 25 | .form-group 26 | label 27 | display block 28 | label.radio-inline 29 | display inline 30 | .container 31 | font-family: 'Roboto', sans-serif 32 | text-align: center; 33 | .pagination 34 | span 35 | height: 30px 36 | width: 30px 37 | color: rgba(0,0,0,.5) 38 | cursor: pointer 39 | display: inline-block 40 | line-height: 30px 41 | 42 | span.active 43 | // background: #2196F3 44 | border-radius: 50% 45 | color: #ECF0F1 46 | background-color: #3498db 47 | 48 | span.active:hover 49 | color: #ECF0F1 50 | 51 | span:first-child 52 | margin-left: 10px 53 | 54 | span:last-child 55 | margin-right: 10px 56 | 57 | font-size: 20px 58 | text-align: center 59 | -------------------------------------------------------------------------------- /public/admin/code/components/camel-to-human/camel-to-human.filter.js: -------------------------------------------------------------------------------- 1 | angular.module('meanbaseApp') 2 | .filter('camelToHuman', function() { 3 | return function(input) { 4 | return input.charAt(0).toLowerCase() + input.substr(1).replace(/[A-Z]/g, function(x) { 5 | return ' ' + x.toLowerCase(); 6 | }); 7 | } 8 | }); 9 | -------------------------------------------------------------------------------- /public/admin/code/components/camel-to-human/camel-to-human.filter.spec.js: -------------------------------------------------------------------------------- 1 | describe('Filter: camelToHuman', function () { 2 | 3 | // load the filter's module 4 | beforeEach(module('meanbaseApp')); 5 | 6 | // initialize a new instance of the filter before each test 7 | var camelToHuman; 8 | beforeEach(inject(function ($filter) { 9 | camelToHuman = $filter('camelToHuman'); 10 | })); 11 | 12 | it('should return the input prefixed with "camelToHuman filter:"', function () { 13 | var text = 'angularjs'; 14 | expect(camelToHuman(text)).toBe('camelToHuman filter: ' + text); 15 | }); 16 | 17 | }); 18 | -------------------------------------------------------------------------------- /public/admin/code/components/date-picker/date-picker.directive.js: -------------------------------------------------------------------------------- 1 | angular.module('meanbaseApp').directive('datePicker', function($window) { 2 | return { 3 | restrict: 'A', 4 | scope: { 5 | options: '=' 6 | }, 7 | link: function(scope, element, attrs) { 8 | var options = Object.assign({ 9 | time: false 10 | }, scope.options) 11 | element.bootstrapMaterialDatePicker(options); 12 | } 13 | }; 14 | 15 | }); 16 | -------------------------------------------------------------------------------- /public/admin/code/components/dialog/dialog.directive.js: -------------------------------------------------------------------------------- 1 | angular.module('meanbaseApp') 2 | .directive('dialogOpen', function ($rootScope, $timeout) { 3 | return { 4 | restrict: 'A', 5 | scope: { 6 | dialogOpen:'=' 7 | }, 8 | link: function (scope, element, attrs) { 9 | var el = element.get(0); 10 | 11 | // dialogPolyfill.registerDialog(el); 12 | 13 | // scope.$watch('dialogOpen', function(value, oldValue) { 14 | // if(value) { 15 | // $timeout(function() { 16 | // el.showModal(); 17 | // }); 18 | // } else if(value !== undefined) { 19 | // $timeout(function() { 20 | // el.close(); 21 | // }); 22 | // } 23 | // }); 24 | } //link 25 | }; //return 26 | }); 27 | -------------------------------------------------------------------------------- /public/admin/code/components/mdl/mdl.directive.js: -------------------------------------------------------------------------------- 1 | angular.module('meanbaseApp').directive('mdl', function($timeout) { 2 | return { 3 | restrict: 'A', 4 | compile: function() { 5 | return { 6 | post: function (scope, element) { 7 | // var el = element.get(0); 8 | var el = element; 9 | $timeout(function() { 10 | componentHandler.upgradeAllRegistered(); 11 | }, 0, false); 12 | } 13 | }; 14 | }, 15 | }; 16 | }) 17 | -------------------------------------------------------------------------------- /public/admin/code/extensions/extensions.controller.spec.js: -------------------------------------------------------------------------------- 1 | describe('Controller: ExtensionsCtrl', function () { 2 | 3 | // load the controller's module 4 | beforeEach(module('meanbaseApp')); 5 | 6 | var ExtensionsCtrl, scope; 7 | 8 | // Initialize the controller and a mock scope 9 | beforeEach(inject(function ($controller, $rootScope) { 10 | scope = $rootScope.$new(); 11 | ExtensionsCtrl = $controller('ExtensionsCtrl', { 12 | $scope: scope 13 | }); 14 | })); 15 | 16 | it('should ...', function () { 17 | expect(1).toEqual(1); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /public/admin/code/extensions/extensions.js: -------------------------------------------------------------------------------- 1 | angular.module('meanbaseApp') 2 | .config(function ($stateProvider) { 3 | $stateProvider 4 | .state('cms.extensions', { 5 | url: '/extensions', 6 | templateUrl: require('./extensions.jade'), 7 | controller: 'ExtensionsCtrl', 8 | hasPermission: 'manageExtensions', 9 | icon: 'input' 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /public/admin/code/extensions/extensions.styl: -------------------------------------------------------------------------------- 1 | #meanbase-cms 2 | .drop-zone 3 | background-color #3498DB 4 | color #ECF0F1 5 | .extension 6 | .mdl-card 7 | width 100% 8 | img 9 | width 100% 10 | i 11 | float right 12 | margin 0.2em 13 | .float-left 14 | float left 15 | 16 | .template-tags 17 | vertical-align middle 18 | display inline-block 19 | min-width 80% 20 | margin-bottom 0 21 | padding 0 0.5em 22 | 23 | .template-label 24 | vertical-align middle 25 | margin-bottom 0 26 | width 80px 27 | margin-right 1em 28 | 29 | .your-templates-label 30 | width 80px 31 | margin-right 1em 32 | 33 | tags-input 34 | .tags 35 | padding 0 36 | margin 0 37 | background-color transparent 38 | box-shadow none 39 | border none 40 | outline none 41 | &.focused 42 | box-shadow none 43 | -webkit-box-shadow none 44 | outline none 45 | .input 46 | border-bottom 2px solid grey 47 | background-color transparent 48 | .tag-list 49 | .tag-item 50 | height auto 51 | border none 52 | background none 53 | background-color white 54 | padding 0 0.5em 55 | -------------------------------------------------------------------------------- /public/admin/code/import/import.controller.spec.js: -------------------------------------------------------------------------------- 1 | describe('Controller: ImportCtrl', function () { 2 | 3 | // load the controller's module 4 | beforeEach(module('meanbaseApp')); 5 | 6 | var ImportCtrl, scope; 7 | 8 | // Initialize the controller and a mock scope 9 | beforeEach(inject(function ($controller, $rootScope) { 10 | scope = $rootScope.$new(); 11 | ImportCtrl = $controller('ImportCtrl', { 12 | $scope: scope 13 | }); 14 | })); 15 | 16 | it('should ...', function () { 17 | expect(1).toEqual(1); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /public/admin/code/import/import.js: -------------------------------------------------------------------------------- 1 | angular.module('meanbaseApp') 2 | .config(function ($stateProvider) { 3 | $stateProvider 4 | .state('cms.import', { 5 | url: '/import', 6 | templateUrl: require('./import.jade'), 7 | controller: 'ImportCtrl', 8 | hasPermission: "importExportData", 9 | icon: 'file_download' 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /public/admin/code/import/import.styl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingfriend1/meanbase/6639e49f9c7eae7cdf5b4a1c7ff17f8ed9c0215f/public/admin/code/import/import.styl -------------------------------------------------------------------------------- /public/admin/code/media/media.controller.js: -------------------------------------------------------------------------------- 1 | angular.module('meanbaseApp') 2 | .controller('MediaCtrl', function ($scope, endpoints, FileUploader, $timeout, $cookieStore, $rootScope, toastr, Auth) { 3 | 4 | $scope.$parent.pageTitle = 'Upload images'; 5 | $scope.media = []; 6 | // $scope.allOperations = true; 7 | 8 | $scope.imageSelectorConfig = { 9 | multiple: true, 10 | allOperations: true 11 | }; 12 | 13 | if (Auth.getToken()) { 14 | var uploader = $scope.uploader = new FileUploader({ 15 | url: '/api/image-uploads', 16 | headers: { 17 | 'Authorization': 'Bearer ' + Auth.getToken() 18 | }, 19 | autoUpload: true 20 | }); 21 | } 22 | 23 | var err = null; 24 | 25 | uploader.onCompleteAll = function(res) { 26 | console.log("res", res); 27 | if(err) { 28 | toastr.error('Failed to upload'); 29 | } else { 30 | toastr.success('Successfully uploaded'); 31 | } 32 | uploader.clearQueue() 33 | }; 34 | 35 | uploader.onCompleteItem = function(res) { 36 | err = res.isError; 37 | $rootScope.$emit('cms.imagesUploaded'); 38 | }; 39 | }); 40 | -------------------------------------------------------------------------------- /public/admin/code/media/media.controller.spec.js: -------------------------------------------------------------------------------- 1 | describe('Controller: MediaCtrl', function () { 2 | 3 | // load the controller's module 4 | beforeEach(module('meanbaseApp')); 5 | 6 | var MediaCtrl, scope; 7 | 8 | // Initialize the controller and a mock scope 9 | beforeEach(inject(function ($controller, $rootScope) { 10 | scope = $rootScope.$new(); 11 | MediaCtrl = $controller('MediaCtrl', { 12 | $scope: scope 13 | }); 14 | })); 15 | 16 | it('should ...', function () { 17 | expect(1).toEqual(1); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /public/admin/code/media/media.jade: -------------------------------------------------------------------------------- 1 | .mdl-grid 2 | .mdl-cell.mdl-cell--12-col 3 | div.drop-zone(nv-file-drop="uploader" nv-file-over multiple uploader="uploader").text-center.form-inline 4 | p Drop files here to upload or 5 | .form-group.text-center 6 | input#file.form-control(type='file' nv-file-select multiple uploader="uploader" style="display: none") 7 | label.mdl-button.mdl-js-button.mdl-button--fab.mdl-button--colored.file-upload-btn(for="file") 8 | i.material-icons add 9 | .progress 10 | .progress-bar(role="progressbar" ng-style="{ 'width': uploader.progress + '%' }") 11 | .mdl-grid 12 | .mdl-cell.mdl-cell--12-col 13 | mb-image-selector(mb-image-selector-config="imageSelectorConfig") 14 | -------------------------------------------------------------------------------- /public/admin/code/media/media.js: -------------------------------------------------------------------------------- 1 | angular.module('meanbaseApp') 2 | .config(function ($stateProvider) { 3 | $stateProvider 4 | .state('cms.media', { 5 | url: '/media', 6 | templateUrl: require('./media.jade'), 7 | controller: 'MediaCtrl', 8 | hasPermission: 'manageMedia', 9 | icon: 'images' 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /public/admin/code/media/media.styl: -------------------------------------------------------------------------------- 1 | #meanbase-cms 2 | .drop-zone 3 | padding 1em 4 | background-color CLOUDS 5 | border 2px dashed CONCRETE 6 | p 7 | margin-bottom 0 8 | padding 0 9 | 10 | .progress 11 | margin-top 0.5em 12 | margin-bottom 0 13 | .nv-file-over 14 | background-color SILVER 15 | border-color CLOUDS -------------------------------------------------------------------------------- /public/admin/code/pages/pages.controller.spec.js: -------------------------------------------------------------------------------- 1 | describe('Controller: CommentsCtrl', function () { 2 | 3 | // load the controller's module 4 | beforeEach(module('meanbaseApp')); 5 | 6 | var CommentsCtrl, scope; 7 | 8 | // Initialize the controller and a mock scope 9 | beforeEach(inject(function ($controller, $rootScope) { 10 | scope = $rootScope.$new(); 11 | CommentsCtrl = $controller('CommentsCtrl', { 12 | $scope: scope 13 | }); 14 | })); 15 | 16 | it('should ...', function () { 17 | expect(1).toEqual(1); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /public/admin/code/pages/pages.js: -------------------------------------------------------------------------------- 1 | angular.module('meanbaseApp') 2 | .config(function ($stateProvider) { 3 | $stateProvider 4 | .state('cms.pages', { 5 | url: '/pages', 6 | templateUrl: require('./pages.jade'), 7 | controller: 'PagesCtrl', 8 | hasPermission: 'editContent', 9 | icon: 'web_asset' 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /public/admin/code/pages/pages.styl: -------------------------------------------------------------------------------- 1 | #meanbase-cms 2 | .mb-list-explanation 3 | position: relative 4 | padding 16px 5 | label 6 | display inline-block 7 | 8 | .mb-publish-label 9 | position: absolute 10 | right 40px 11 | .margin-top 12 | margin-top 1em 13 | .panel-group 14 | .panel 15 | overflow initial 16 | .commment-author 17 | padding-left 0.75em 18 | .date-field 19 | margin-bottom 0 20 | #moderate-comments 21 | .form-group 22 | .btn 23 | margin-right 0.5em 24 | margin-bottom 0.5em 25 | -------------------------------------------------------------------------------- /public/admin/code/themes/themes.controller.spec.js: -------------------------------------------------------------------------------- 1 | describe('Controller: ThemesCtrl', function () { 2 | 3 | // load the controller's module 4 | beforeEach(module('meanbaseApp')); 5 | 6 | var ThemesCtrl, scope; 7 | 8 | // Initialize the controller and a mock scope 9 | beforeEach(inject(function ($controller, $rootScope) { 10 | scope = $rootScope.$new(); 11 | ThemesCtrl = $controller('ThemesCtrl', { 12 | $scope: scope 13 | }); 14 | })); 15 | 16 | it('should ...', function () { 17 | expect(1).toEqual(1); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /public/admin/code/themes/themes.js: -------------------------------------------------------------------------------- 1 | angular.module('meanbaseApp') 2 | .config(function ($stateProvider) { 3 | $stateProvider 4 | .state('cms.themes', { 5 | url: '/themes', 6 | templateUrl: require('./themes.jade'), 7 | controller: 'ThemesCtrl', 8 | hasPermission: 'changeSiteSettings', 9 | icon: 'settings' 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /public/admin/code/themes/themes.styl: -------------------------------------------------------------------------------- 1 | #meanbase-cms 2 | .drop-zone 3 | background-color #3498DB 4 | color #ECF0F1 5 | .theme 6 | .mdl-card 7 | width 100% 8 | img 9 | width 100% 10 | i 11 | float right 12 | margin 0.2em 13 | .no-margin 14 | margin 0 !important 15 | .float-left 16 | float left 17 | 18 | .template-tags 19 | vertical-align middle 20 | display inline-block 21 | min-width 80% 22 | margin-bottom 0 23 | padding 0 0.5em 24 | 25 | .template-label 26 | vertical-align middle 27 | margin-bottom 0 28 | width 80px 29 | margin-right 1em 30 | 31 | .your-templates-label 32 | width 80px 33 | margin-right 1em 34 | 35 | tags-input 36 | .tags 37 | padding 0 38 | margin 0 39 | background-color transparent 40 | box-shadow none 41 | border none 42 | outline none 43 | &.focused 44 | box-shadow none 45 | -webkit-box-shadow none 46 | outline none 47 | .input 48 | border-bottom 2px solid grey 49 | background-color transparent 50 | .tag-list 51 | .tag-item 52 | height auto 53 | border none 54 | background none 55 | background-color white 56 | padding 0 0.5em 57 | -------------------------------------------------------------------------------- /public/admin/code/users/users.controller.spec.js: -------------------------------------------------------------------------------- 1 | describe('Controller: UsersCtrl', function () { 2 | 3 | // load the controller's module 4 | beforeEach(module('meanbaseApp')); 5 | 6 | var UsersCtrl, scope; 7 | 8 | // Initialize the controller and a mock scope 9 | beforeEach(inject(function ($controller, $rootScope) { 10 | scope = $rootScope.$new(); 11 | UsersCtrl = $controller('UsersCtrl', { 12 | $scope: scope 13 | }); 14 | })); 15 | 16 | it('should ...', function () { 17 | expect(1).toEqual(1); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /public/admin/code/users/users.js: -------------------------------------------------------------------------------- 1 | angular.module('meanbaseApp') 2 | .config(function ($stateProvider) { 3 | $stateProvider 4 | .state('cms.users', { 5 | url: '/users', 6 | templateUrl: require('./users.jade'), 7 | controller: 'UsersCtrl', 8 | controllerAs:'stateCtrl', 9 | hasPermission: 'manageUsers', 10 | icon: 'people' 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /public/admin/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingfriend1/meanbase/6639e49f9c7eae7cdf5b4a1c7ff17f8ed9c0215f/public/admin/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /public/admin/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingfriend1/meanbase/6639e49f9c7eae7cdf5b4a1c7ff17f8ed9c0215f/public/admin/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /public/admin/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingfriend1/meanbase/6639e49f9c7eae7cdf5b4a1c7ff17f8ed9c0215f/public/admin/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /public/admin/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingfriend1/meanbase/6639e49f9c7eae7cdf5b4a1c7ff17f8ed9c0215f/public/admin/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /public/admin/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingfriend1/meanbase/6639e49f9c7eae7cdf5b4a1c7ff17f8ed9c0215f/public/admin/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /public/app/app.styl: -------------------------------------------------------------------------------- 1 | // 2 | // Bootstrap Fonts 3 | // 4 | 5 | @font-face 6 | font-family: 'Glyphicons Halflings' 7 | src: url('./bower_components/bootstrap/fonts/glyphicons-halflings-regular.eot') 8 | src: url('./bower_components/bootstrap/fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'), 9 | url('./bower_components/bootstrap/fonts/glyphicons-halflings-regular.woff') format('woff'), 10 | url('./bower_components/bootstrap/fonts/glyphicons-halflings-regular.ttf') format('truetype'), 11 | url('./bower_components/bootstrap/fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg'); 12 | 13 | // 14 | // Font Awesome Fonts 15 | // 16 | 17 | @font-face 18 | font-family: 'FontAwesome' 19 | src: url('./bower_components/font-awesome/fonts/fontawesome-webfont.eot?v=4.1.0') 20 | src: url('./bower_components/font-awesome/fonts/fontawesome-webfont.eot?#iefix&v=4.1.0') format('embedded-opentype'), 21 | url('./bower_components/font-awesome/fonts/fontawesome-webfont.woff?v=4.1.0') format('woff'), 22 | url('./bower_components/font-awesome/fonts/fontawesome-webfont.ttf?v=4.1.0') format('truetype'), 23 | url('./bower_components/font-awesome/fonts/fontawesome-webfont.svg?v=4.1.0#fontawesomeregular') format('svg'); 24 | font-weight: normal 25 | font-style: normal 26 | -------------------------------------------------------------------------------- /public/app/components/cms.headbar/choose-link.modal.jade: -------------------------------------------------------------------------------- 1 | .modal-header 2 | button.close(type='button' ng-click="cancel()") 3 | span(aria-hidden='true') × 4 | span.sr-only Close 5 | h4.modal-title Choose your page link name 6 | .modal-body 7 | .form-group 8 | label Link Title 9 | .input-group 10 | span.input-group-addon / 11 | input.form-control(type='text' ng-model="url" autofocus ng-enter="choose(url)") 12 | span.glyphicon.glyphicon-ok.form-control-feedback 13 | .modal-footer 14 | button.btn.btn-default(type='button' ng-click="cancel()") Close 15 | button.btn.btn-success(type='button' ng-click="choose(url)") Choose 16 | -------------------------------------------------------------------------------- /public/app/components/cms.headbar/editmodal.modal.jade: -------------------------------------------------------------------------------- 1 | .modal-header 2 | button.close(type='button' ng-click="save()") 3 | span(aria-hidden='true') × 4 | span.sr-only Close 5 | h4.modal-title Page Settings 6 | .modal-body 7 | .form-group 8 | label(for='page-template') Page template: 9 | select#page-template.form-control(ng-model="page.template" ng-options="option for option in templateOptions") 10 | .form-group(validate="{{errorMessages.URI}}").has-feedback 11 | label Page Link URL 12 | .input-group 13 | span.input-group-addon / 14 | input.form-control(type='text' ng-model="page.url" ng-pattern="validators.isURI" ng-change="updatePageTitle(page.url)") 15 | span.glyphicon.glyphicon-ok.form-control-feedback 16 | .form-group(validate="{{errorMessages.isTitle}}").has-feedback 17 | label Search Engine (Google, Yahoo...) and Tab Title 18 | input.form-control(type='text' ng-model="page.tabTitle" ng-pattern="validators.isTitle") 19 | span.glyphicon.glyphicon-ok.form-control-feedback 20 | .form-group 21 | label Search Engine Description 22 | textarea.form-control(rows="5" ng-model="page.description") 23 | .modal-footer 24 | .col-sm-9 25 | p.h6.text-left (The headbar save and discard buttons will store changes) 26 | .col-sm-3 27 | button.btn.btn-success(type='button' ng-click="save()") Save 28 | -------------------------------------------------------------------------------- /public/app/components/main/main.controller.spec.js: -------------------------------------------------------------------------------- 1 | describe('Controller: MainCtrl', function () { 2 | 3 | // load the controller's module 4 | beforeEach(module('meanbaseApp')); 5 | 6 | var MainCtrl, 7 | scope, 8 | $httpBackend; 9 | 10 | // Initialize the controller and a mock scope 11 | beforeEach(inject(function (_$httpBackend_, $controller, $rootScope) { 12 | $httpBackend = _$httpBackend_; 13 | $httpBackend.expectGET('/api/things') 14 | .respond(['HTML5 Boilerplate', 'AngularJS', 'Karma', 'Express']); 15 | 16 | scope = $rootScope.$new(); 17 | MainCtrl = $controller('MainCtrl', { 18 | $scope: scope 19 | }); 20 | })); 21 | 22 | it('should attach a list of things to the scope', function () { 23 | $httpBackend.flush(); 24 | expect(scope.awesomeThings.length).toBe(4); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /public/app/components/main/main.jade: -------------------------------------------------------------------------------- 1 | .meanbase-front#mb-meanbase-front(ng-class="{loggedIn: isLoggedIn}") 2 | include ../cms.headbar/cms.headbar.jade 3 | div(ui-view="" ng-class="{'fixed-header-stopper': isLoggedIn}")#main-view 4 | div.mb-loading-screen.text-center 5 | h1 6 | i.fa.fa-spinner.fa-spin 7 | span| Loading Template 8 | -------------------------------------------------------------------------------- /public/app/components/mb-add-menu-item/mb-add-menu-item.controller.js: -------------------------------------------------------------------------------- 1 | module.exports = function linkModalController($scope, $modalInstance, api, menu, property, $rootScope) { 2 | 3 | api.pages.find({$select: ['url', 'title', '_id']}).then(function(response) { 4 | $scope.pages = response 5 | }) 6 | 7 | $scope.link = { 8 | target: '_blank' 9 | } 10 | 11 | $scope.searchTitle = '' 12 | 13 | $scope.toggleSelected = function(page) { 14 | page.selected = !page.selected 15 | } 16 | 17 | $scope.selectedLinks = {} 18 | 19 | $scope.addMenuItems = function(editLinkForm) { 20 | 21 | if(!editLinkForm.$valid) { return false } 22 | 23 | let selectedPages = [] 24 | let position = -1 25 | $scope.pages.forEach(function(page) { 26 | if(page.selected) { 27 | selectedPages.push({ 28 | title: page.title.replace(/<[^>]+>/gm, ''), 29 | url: page.url, 30 | position: menu[property].length + ++position, 31 | group: property, 32 | linkTo: page._id, 33 | classes: $scope.selectedLinks.classes, 34 | iconClasses: $scope.selectedLinks.iconClasses, 35 | subMenus: [] 36 | }) 37 | } 38 | }); 39 | 40 | if(selectedPages.length > 0) { 41 | 42 | menu[property] = menu[property].concat(selectedPages) 43 | $modalInstance.dismiss() 44 | } else if((_.get($scope.link, 'url') || _.get($scope.link, 'isDropdown')) && _.get($scope.link, 'title') ) { 45 | if($scope.link.isDropdown) { 46 | $scope.link.subMenus = [] 47 | } 48 | menu[property].push($scope.link); 49 | $modalInstance.dismiss() 50 | } 51 | 52 | $rootScope.$emit('cms.elementsChanged') 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /public/app/components/mb-add-menu-item/mb-add-menu-item.directive.js: -------------------------------------------------------------------------------- 1 | angular.module('meanbaseApp') 2 | .directive('mbAddMenuItem', function ($rootScope, endpoints, $timeout, addMenuModal) { 3 | return { 4 | template: ' Add Link', 5 | restrict: 'A', 6 | link: function (scope, element, attrs) { 7 | 8 | element.bind('click', function(event) { 9 | let group = scope.$parent.$eval(attrs.mbAddMenuItem) 10 | 11 | if(!group) { 12 | if(!$rootScope.menus[attrs.mbAddMenuItem]) { 13 | $rootScope.menus[attrs.mbAddMenuItem] = [] 14 | } 15 | group = attrs.mbAddMenuItem 16 | addMenuModal.open($rootScope.menus, group) 17 | } else { 18 | addMenuModal.open(group, attrs.property) 19 | } 20 | 21 | 22 | 23 | }); 24 | } 25 | } 26 | 27 | }); 28 | -------------------------------------------------------------------------------- /public/app/components/mb-add-menu-item/mb-add-menu-item.service.js: -------------------------------------------------------------------------------- 1 | (() => { 2 | 3 | @mbService() 4 | 5 | class addMenuModal { 6 | 7 | constructor($rootScope, $modal) { 8 | this.$rootScope = $rootScope 9 | this.$modal = $modal 10 | } 11 | 12 | @autobind 13 | open(belongsTo, property) { 14 | if(!belongsTo || !property || !belongsTo[property]) { return false } 15 | 16 | var modalInstance = this.$modal.open({ 17 | templateUrl: require('./mb-add-menu-item.modal.jade'), 18 | controller: require('./mb-add-menu-item.controller.js'), 19 | size: 'md', 20 | resolve: { 21 | property: function() { 22 | return property 23 | }, 24 | menu: function() { 25 | return belongsTo 26 | } 27 | } 28 | }) 29 | } 30 | } 31 | 32 | })() 33 | -------------------------------------------------------------------------------- /public/app/components/mb-choose-icon/mb-choose-icon.styl: -------------------------------------------------------------------------------- 1 | .select-images-button 2 | margin-top 1em 3 | 4 | .inEditMode choose-icon 5 | cursor pointer 6 | -------------------------------------------------------------------------------- /public/app/components/mb-choose-icon/mb-edit-icon.service.js: -------------------------------------------------------------------------------- 1 | (() => { 2 | 3 | @mbService() 4 | 5 | class iconModal { 6 | 7 | constructor($rootScope, $modal, $location) { 8 | this.$rootScope = $rootScope 9 | this.$modal = $modal 10 | this.$location = $location 11 | } 12 | 13 | @autobind 14 | open($event, item, property, href) { 15 | if(this.$rootScope.editMode) { 16 | if(!item[property]) { 17 | item[property] = {} 18 | } 19 | $event.preventDefault() 20 | var modalInstance = this.$modal.open({ 21 | templateUrl: require('./mb-edit-icon.modal.jade'), 22 | controller: require('./mb-edit-icon.controller.js'), 23 | size: 'md', 24 | resolve: { 25 | icon: function() { 26 | return item[property] 27 | }, 28 | } 29 | }) 30 | } else { 31 | if(item[property].target) { 32 | window.open(href, item[property].target) 33 | } else { 34 | this.$location.path(href) 35 | } 36 | } 37 | } 38 | } 39 | 40 | })() 41 | -------------------------------------------------------------------------------- /public/app/components/mb-choose-image/mb-choose-image.styl: -------------------------------------------------------------------------------- 1 | .select-images-button 2 | margin-top 1em 3 | 4 | .inEditMode choose-image 5 | cursor pointer 6 | -------------------------------------------------------------------------------- /public/app/components/mb-choose-link/mb-choose-link.directive.js: -------------------------------------------------------------------------------- 1 | angular.module('meanbaseApp') 2 | .directive('mbChooseLink', function ($rootScope, endpoints, $timeout, linkModal) { 3 | return { 4 | template: '', 5 | transclude: true, 6 | // replace: true, 7 | restrict: 'AE', 8 | scope: true, 9 | link: function (scope, element, attrs) { 10 | 11 | if(!$rootScope.isLoggedIn) { return false; } 12 | 13 | scope.belongsTo = scope.$eval(attrs.belongsTo); 14 | 15 | if(!scope.belongsTo) { scope.belongsTo = {}; } 16 | if(!scope.belongsTo[attrs.property]) { scope.belongsTo[attrs.property] = {}; } 17 | 18 | var chosenElement = element; 19 | 20 | var insideLink; 21 | if(element.find('.mb-link')) { 22 | insideLink = element.find('.mb-link:first')[0]; 23 | } 24 | 25 | function fireClick(event) { 26 | if(!$rootScope.editMode) { return false; } 27 | if(event.target !== element && event.target !== insideLink) { return false; } 28 | event.preventDefault(); 29 | scope.belongsTo = scope.$eval(attrs.belongsTo); 30 | if(!scope.belongsTo[attrs.property]) { scope.belongsTo[attrs.property] = {}; } 31 | linkModal.open(scope.belongsTo, attrs.property) 32 | } 33 | 34 | element.bind('click', fireClick); 35 | } 36 | } 37 | 38 | }); 39 | -------------------------------------------------------------------------------- /public/app/components/mb-choose-link/mb-choose-link.styl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingfriend1/meanbase/6639e49f9c7eae7cdf5b4a1c7ff17f8ed9c0215f/public/app/components/mb-choose-link/mb-choose-link.styl -------------------------------------------------------------------------------- /public/app/components/mb-choose-link/mb-edit-link.controller.js: -------------------------------------------------------------------------------- 1 | module.exports = function linkModalController($scope, $modalInstance, link, api, $rootScope) { 2 | api.pages.find({$select: ['url']}).then(function(response) { 3 | $scope.pages = response 4 | }) 5 | 6 | $scope.link = angular.copy(link) 7 | 8 | $scope.updateTarget = function(url) { 9 | if(url.indexOf('http://') > -1 || url.indexOf('https://') > -1) { 10 | if(!$scope.link.target) { 11 | $scope.link.target = '_blank' 12 | } 13 | } else { 14 | $scope.link.target = "" 15 | } 16 | } 17 | 18 | $scope.saveLink = function(editLinkForm) { 19 | 20 | // We want to make sure the changes are valid before submitting it 21 | if(editLinkForm.$valid) { 22 | // link is the menu that was passed in (the actual menu we want to modify). $scope.link is the object that's being edited in the modal. 23 | link.title = $scope.link.title || link.title 24 | link.url = $scope.link.url || link.url 25 | link.classes = $scope.link.classes 26 | link.target = $scope.link.target 27 | $rootScope.$emit('cms.elementsChanged') 28 | $modalInstance.dismiss() 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /public/app/components/mb-choose-link/mb-edit-link.service.js: -------------------------------------------------------------------------------- 1 | (() => { 2 | 3 | @mbService() 4 | 5 | class linkModal { 6 | 7 | constructor($rootScope, $modal) { 8 | this.$rootScope = $rootScope 9 | this.$modal = $modal 10 | } 11 | 12 | @autobind 13 | open(belongsTo, property) { 14 | if(!belongsTo || !property) { return false } 15 | var modalInstance = this.$modal.open({ 16 | templateUrl: require('./mb-edit-link.modal.jade'), 17 | controller: require('./mb-edit-link.controller.js'), 18 | size: 'md', 19 | resolve: { 20 | link: function() { 21 | return belongsTo[property] 22 | }, 23 | } 24 | }) 25 | } 26 | } 27 | 28 | })() 29 | -------------------------------------------------------------------------------- /public/app/components/mb-dynamic-html/mb-dynamic-html.directive.js: -------------------------------------------------------------------------------- 1 | angular.module('meanbaseApp').directive('mbDynamicHtml', function ($compile) { 2 | return { 3 | restrict: 'A', 4 | link: function (scope, element, attrs) { 5 | scope.$watch(attrs.mbDynamicHtml, function(html) { 6 | element.prepend(html); 7 | $compile(element.contents())(scope); 8 | }) 9 | } 10 | }; 11 | }); 12 | -------------------------------------------------------------------------------- /public/app/components/mb-dynamic-html/mb-dynamic-html.directive.spec.js: -------------------------------------------------------------------------------- 1 | describe('Directive: dynamicHtml', function () { 2 | 3 | // load the directive's module 4 | beforeEach(module('meanbaseApp')); 5 | 6 | var element, 7 | scope; 8 | 9 | beforeEach(inject(function ($rootScope) { 10 | scope = $rootScope.$new(); 11 | })); 12 | 13 | it('should make hidden element visible', inject(function ($compile) { 14 | element = angular.element(''); 15 | element = $compile(element)(scope); 16 | expect(element.text()).toBe('this is the dynamicHtml directive'); 17 | })); 18 | }); 19 | -------------------------------------------------------------------------------- /public/app/components/mb-edit-link/mb-edit-link.directive.js: -------------------------------------------------------------------------------- 1 | angular.module('meanbaseApp') 2 | .directive('mbEditLink', function ($rootScope, endpoints, $timeout) { 3 | return { 4 | template: '', 5 | restrict: 'EA', 6 | link: function (scope, element, attrs) { 7 | 8 | if(!$rootScope.isLoggedIn) { return false } 9 | 10 | element.bind('click', function(event) { 11 | scope.belongsTo = scope.$eval(attrs.belongsTo) 12 | if(!scope.belongsTo) { scope.belongsTo = {} } 13 | if(!scope.belongsTo[attrs.property]) { scope.belongsTo[attrs.property] = {} } 14 | scope.openLinkModal(scope.belongsTo, attrs.property) 15 | }) 16 | } 17 | } 18 | 19 | }) 20 | -------------------------------------------------------------------------------- /public/app/components/mb-edit-link/mb-edit-link.styl: -------------------------------------------------------------------------------- 1 | .mb-edit-link-btn 2 | position: absolute 3 | top: -20px 4 | right: 30px 5 | padding-top: 2px 6 | padding-left: 2px 7 | width: 35px 8 | height: 30px 9 | border-radius: 3px 10 | background-color: #95A5A6 11 | color: #ECF0F1 12 | cursor: pointer 13 | z-index: 2 14 | -------------------------------------------------------------------------------- /public/app/components/mb-edit-menu/mb-edit-menu.directive.js: -------------------------------------------------------------------------------- 1 | angular.module('meanbaseApp') 2 | .directive('mbEditMenu', function ($rootScope, endpoints, $timeout, editMenuModal) { 3 | return { 4 | template: '
', 5 | restrict: 'EA', 6 | link: function (scope, element, attrs) { 7 | scope.item = scope.$eval(attrs.item) 8 | 9 | element.bind('click', function(event) { 10 | scope.item = scope.$eval(attrs.item) 11 | editMenuModal.open(event, scope.item) 12 | }) 13 | } 14 | } 15 | 16 | }) 17 | -------------------------------------------------------------------------------- /public/app/components/mb-edit-menu/mb-edit-menu.service.js: -------------------------------------------------------------------------------- 1 | (() => { 2 | 3 | @mbService() 4 | class editMenuModal { 5 | 6 | constructor($rootScope, $modal, $location) { 7 | this.$rootScope = $rootScope 8 | this.$modal = $modal 9 | this.$location = $location 10 | } 11 | 12 | @autobind 13 | open($event, menuItem, href) { 14 | 15 | if(this.$rootScope.editMode) { 16 | 17 | $event.preventDefault() 18 | var modalInstance = this.$modal.open({ 19 | templateUrl: require('./mb-edit-menu.modal.jade'), 20 | controller: require('./mb-edit-menu.controller.js'), 21 | size: 'md', 22 | resolve: { 23 | menuItem: function() { 24 | return menuItem 25 | }, 26 | isNewMenu: function() { 27 | return false 28 | } 29 | } 30 | }) 31 | 32 | } else { 33 | 34 | if($event.target.classList.contains('mb-edit-menu-btn')) { console.log("false", false); return false } 35 | if(menuItem.target) { 36 | window.open(href, menuItem.target) 37 | } else { 38 | this.$location.path(href) 39 | } 40 | 41 | } 42 | 43 | } 44 | } 45 | 46 | })() 47 | -------------------------------------------------------------------------------- /public/app/components/mb-edit-menu/mb-edit-menu.styl: -------------------------------------------------------------------------------- 1 | .mb-edit-menu-btn 2 | // position: absolute 3 | display inline-block 4 | padding-top: 2px 5 | padding-left: 5px 6 | width: 22px 7 | height: 22px 8 | border-radius: 10em 9 | background-color: #95A5A6 10 | color: #ECF0F1 11 | cursor: pointer 12 | position absolute 13 | top 0 14 | right 0 15 | i 16 | color #ECF0F1 17 | -------------------------------------------------------------------------------- /public/app/components/mb-extension-edit/mb-extension-edit.directive.js: -------------------------------------------------------------------------------- 1 | angular.module('meanbaseApp') 2 | .directive('mbExtensionEdit', function ($rootScope, endpoints, $timeout, $modal, editExtensionModal) { 3 | return { 4 | template: '
', 5 | restrict: 'EA', 6 | scope: true, 7 | link: function (scope, element, attrs) { 8 | 9 | if(!$rootScope.isLoggedIn) { return false; } 10 | 11 | var item = scope.$parent.$eval(attrs.item); 12 | 13 | element.bind('click', function() { 14 | console.log('click modal'); 15 | editExtensionModal.open(item) 16 | }); 17 | 18 | // scope.$on('$destroy', function() { 19 | // element.unbind('click') 20 | // }) 21 | } 22 | } 23 | 24 | }); 25 | -------------------------------------------------------------------------------- /public/app/components/mb-extension-edit/mb-extension-edit.modal.jade: -------------------------------------------------------------------------------- 1 | .modal-header 2 | button.close(type='button' ng-click="$dismiss()") 3 | span(aria-hidden='true') × 4 | span.sr-only Close 5 | h4#editMenuItemLabel.modal-title Sync Addon with Group 6 | .modal-body 7 | .alert.alert-warning 8 | div Addons can sync data with other addons of the same type. Just choose a group you want to sync with and your data will stay synchronized within that group. 9 | form(novalidate name="editIconForm" ng-submit="updateExtensionKey(editIconForm)") 10 | .checkbox 11 | label 12 | input(type="checkbox" name="sync" ng-model="sync") 13 | | Sync Addon 14 | .form-group(ng-show="sync") 15 | label with group: 16 | select(ng-options="group.key as group.key for group in syncGroups" ng-model="syncGroup").form-control 17 | input(type="submit").hidden 18 | 19 | button.btn.btn-info(type='button', data-toggle='collapse', data-target='#extra-styles') Create new group 20 | #extra-styles.collapse 21 | .form-group 22 | label Create a new group 23 | input.form-control(ng-model="newSyncGroup") 24 | .modal-footer 25 | button.btn.btn-success(type="submit" ng-click="updateExtension(syncGroup, newSyncGroup)" ng-class="{disabled: editIconForm.$invalid || (!hasContent && icon.classes)}") Save 26 | button.btn.btn-default(type='button' ng-click="$dismiss()") Close 27 | -------------------------------------------------------------------------------- /public/app/components/mb-extension-edit/mb-extension-edit.styl: -------------------------------------------------------------------------------- 1 | .mb-edit-extension-btn { 2 | position: absolute; 3 | top: -20px; 4 | right: 0px; 5 | padding-top: 1px; 6 | padding-left: 7px; 7 | width: 35px; 8 | height: 30px; 9 | border-radius: 3px; 10 | background-color: #95A5A6; 11 | color: #ECF0F1; 12 | cursor: pointer; 13 | z-index: 2; 14 | } 15 | -------------------------------------------------------------------------------- /public/app/components/mb-find-images-modal/mb-find-image.modal.jade: -------------------------------------------------------------------------------- 1 | #findImage-modal.extensiondata-selector 2 | .modal-header 3 | button.close(ng-click="$dismiss()") 4 | span(aria-hidden="true") × 5 | span.sr-only Close 6 | h4.modal-title {{instructions}} 7 | .modal-body 8 | mb-image-selector(api="imageSelectorApi" mb-image-selector-config="config") 9 | .modal-footer 10 | p.double-tap-instructions Double tap to enlarge photos 11 | div.choose-close-buttons 12 | button.btn.btn-success(type='button' ng-click="chooseImages()") Choose 13 | button.btn.btn-default(type='button' ng-click="$dismiss()") Close 14 | -------------------------------------------------------------------------------- /public/app/components/mb-find-images-modal/mb-find-image.service.js: -------------------------------------------------------------------------------- 1 | (() => { 2 | 3 | @mbService() 4 | 5 | class imageModal { 6 | 7 | constructor($rootScope, $modal) { 8 | this.$rootScope = $rootScope 9 | this.$modal = $modal 10 | } 11 | 12 | @autobind 13 | open(config, callback) { 14 | var modalInstance = this.$modal.open({ 15 | 16 | templateUrl: require('./mb-find-image.modal.jade'), 17 | 18 | controller: function($scope, $modalInstance, config, $timeout) { 19 | $scope.config = config 20 | 21 | config.allOperations = true 22 | $scope.imageSelectorApi = {} 23 | var areChanges 24 | 25 | if($scope.config.multiple) { 26 | $scope.instructions = 'Choose Images' 27 | } else { 28 | $scope.instructions = 'Choose Image' 29 | } 30 | 31 | 32 | $modalInstance.opened.then(() => { 33 | $timeout(function() { 34 | $scope.imageSelectorApi.getAlreadySelected($scope.config.alreadySelected) 35 | }, 0, true) 36 | 37 | }) 38 | // $scope.allOperations = false 39 | $scope.chooseImages = function() { 40 | areChanges = true 41 | var selectedImages = $scope.imageSelectorApi.getSelectedImages() 42 | $modalInstance.close(selectedImages) 43 | } 44 | }, 45 | size: 'lg', 46 | resolve: { 47 | config: function() { 48 | return config || {} 49 | } 50 | } 51 | }) 52 | 53 | modalInstance.result.then((selectedImages) => { 54 | if(callback) { 55 | callback(selectedImages) 56 | } 57 | }) 58 | } 59 | 60 | } 61 | })() 62 | -------------------------------------------------------------------------------- /public/app/components/mb-find-images-modal/mb-find-images-modal.directive.js: -------------------------------------------------------------------------------- 1 | angular.module('meanbaseApp') 2 | .directive('mbFindImagesModal', function ($rootScope, $timeout, imageModal) { 3 | return { 4 | restrict: 'EA', 5 | scope: true, 6 | link: function (scope, element, attrs) { 7 | if(!$rootScope.isLoggedIn) { return false; } 8 | 9 | var config = scope.findImagesConfig; 10 | 11 | element.bind('click', function openModal(event) { 12 | event.stopPropagation() 13 | if(!$rootScope.editMode) { return false; } 14 | 15 | if (event.target !== this && !$(event.target).is('[mb-src]') && !$(event.target).is('img')) { return }; 16 | 17 | imageModal.open(config, function(selectedImages) { 18 | $rootScope.$emit('cms.choseImages', {gallerySlug: config.gallerySlug, images: selectedImages}); 19 | }) 20 | }); 21 | } 22 | }; 23 | }); 24 | -------------------------------------------------------------------------------- /public/app/components/mb-find-images-modal/mb-find-images-modal.directive.spec.js: -------------------------------------------------------------------------------- 1 | describe('Directive: findImagesModal', function () { 2 | 3 | // load the directive's module and view 4 | beforeEach(module('meanbaseApp')); 5 | beforeEach(module('components/find-images-modal/find-images-modal.html')); 6 | 7 | var element, scope; 8 | 9 | beforeEach(inject(function ($rootScope) { 10 | scope = $rootScope.$new(); 11 | })); 12 | 13 | it('should make hidden element visible', inject(function ($compile) { 14 | element = angular.element(''); 15 | element = $compile(element)(scope); 16 | scope.$apply(); 17 | expect(element.text()).toBe('this is the findImagesModal directive'); 18 | })); 19 | }); 20 | -------------------------------------------------------------------------------- /public/app/components/mb-find-images-modal/mb-find-images-modal.styl: -------------------------------------------------------------------------------- 1 | .disable-click { 2 | pointer-events: none; 3 | } 4 | 5 | .enable-click { 6 | pointer-events: auto; 7 | } 8 | -------------------------------------------------------------------------------- /public/app/components/mb-grid-item/mb-grid-item.styl: -------------------------------------------------------------------------------- 1 | .inEditMode [mb-grid-item] 2 | position relative 3 | box-shadow 2px 2px 2px 1px rgba(0, 0, 0, 0.2) 4 | 5 | // .inEditMode [mb-grid-item] .ui-resizable-handlebefore 6 | // content "\f0dc" 7 | 8 | .inEditMode [mb-grid-item] .ui-resizable-handle 9 | position absolute 10 | font normal normal normal 20px/1 FontAwesome 11 | color black 12 | display block 13 | background-color transparent 14 | left 100% 15 | top 50% 16 | width 12px 17 | height 100% 18 | // -webkit-transform translate(-50%, -50%) rotate(90deg) 19 | // transform translate(-50%, -50%) rotate(90deg) 20 | // -ms-transform translate(-50%, -50%) rotate(90deg) 21 | 22 | -webkit-transform translate(-50%, -50%) 23 | transform translate(-50%, -50%) 24 | -ms-transform translate(-50%, -50%) 25 | cursor col-resize 26 | -------------------------------------------------------------------------------- /public/app/components/mb-icon/mb-icon.directive.js: -------------------------------------------------------------------------------- 1 | angular.module('meanbaseApp') 2 | .directive('mbIcon', function ($rootScope, endpoints, $compile) { 3 | return { 4 | template: '', 5 | restrict: 'E', 6 | scope: true, 7 | replace: true, 8 | link: function (scope, element, attrs) { 9 | scope.belongsTo = scope.$parent.$eval(attrs.belongsTo); 10 | 11 | if(!scope.belongsTo) { scope.belongsTo = {}; } 12 | scope.property = attrs.property; 13 | 14 | if(scope.belongsTo[scope.property].classes === 'fa fa-pencil fa-lg example') { 15 | scope.belongsTo[scope.property].classes = ''; 16 | } 17 | } 18 | } 19 | }); 20 | -------------------------------------------------------------------------------- /public/app/components/mb-init-list/init-list.directive.js: -------------------------------------------------------------------------------- 1 | 2 | angular.module('meanbaseApp') 3 | .directive('mbInitList', function ($rootScope, $timeout) { 4 | return { 5 | restrict: 'A', 6 | scope: { 7 | mbInitList: '=' 8 | }, 9 | link: function (scope, element, attrs) { 10 | function checkForEmpty() { 11 | if(!scope.mbInitList) { 12 | scope.mbInitList = { 13 | items: [] 14 | }; 15 | } 16 | 17 | if(!scope.mbInitList.items) { 18 | scope.mbInitList.items = []; 19 | } 20 | } 21 | 22 | checkForEmpty() 23 | 24 | scope.$onRootScope('cms.updateView', function() { 25 | checkForEmpty() 26 | }) 27 | } 28 | }; 29 | }); 30 | -------------------------------------------------------------------------------- /public/app/components/mb-link/mb-link.directive.js: -------------------------------------------------------------------------------- 1 | angular.module('meanbaseApp') 2 | .directive('mbLink', function ($rootScope, $timeout, $location, Auth) { 3 | return { 4 | restrict: 'A', 5 | template: '', 6 | transclude: true, 7 | replace: true, 8 | scope: { 9 | mbLink: "@", 10 | belongsTo: "=" 11 | }, 12 | link: function (scope, element, attrs) { 13 | scope.belongsTo = scope.$parent.$eval(attrs.belongsTo); 14 | scope.mbLink = attrs.mbLink; 15 | if(!scope.belongsTo) { scope.belongsTo = {}; } 16 | if(!scope.belongsTo[scope.mbLink]) { scope.belongsTo[scope.mbLink] = {}; } 17 | 18 | element.bind('click', _.debounce(function(event) { 19 | if(!$rootScope.editMode) { 20 | var anchorLink = scope.belongsTo[scope.mbLink]; 21 | if(anchorLink.target) { 22 | window.open(anchorLink.url, anchorLink.target); 23 | } else { 24 | $location.path(anchorLink.url); 25 | } 26 | } else { 27 | event.preventDefault() 28 | } 29 | }, 30)) 30 | 31 | if(!Auth.isLoggedIn()) { return false } 32 | 33 | scope.$onRootScope('cms.updateView', function(event, shouldSave) { 34 | scope.belongsTo = scope.$parent.$eval(attrs.belongsTo); 35 | if(!scope.belongsTo) { scope.belongsTo = {}; } 36 | if(!scope.belongsTo[scope.mbLink]) { scope.belongsTo[scope.mbLink] = {}; } 37 | }) 38 | } 39 | } 40 | 41 | }); 42 | -------------------------------------------------------------------------------- /public/app/components/mb-link/mb-link.styl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingfriend1/meanbase/6639e49f9c7eae7cdf5b4a1c7ff17f8ed9c0215f/public/app/components/mb-link/mb-link.styl -------------------------------------------------------------------------------- /public/app/components/mb-list-add/mb-list-add.directive.js: -------------------------------------------------------------------------------- 1 | angular.module('meanbaseApp') 2 | .directive('mbListAdd', function ($rootScope, endpoints, $timeout) { 3 | return { 4 | template: '
Add {{label}}
', 5 | restrict: 'EA', 6 | link: function (scope, element, attrs) { 7 | 8 | scope.list = scope.$eval(attrs.list); 9 | scope.item = scope.$eval(attrs.item); 10 | scope.label = attrs.label; 11 | 12 | if(!$rootScope.isLoggedIn) { return false; } 13 | 14 | if(!scope.label) { scope.label = 'item'; } 15 | 16 | if(!scope.list) { 17 | scope.list = []; 18 | } 19 | 20 | if(!scope.item) { 21 | scope.item = {}; 22 | } 23 | 24 | element.bind('click', function(event) { 25 | scope.item = scope.$eval(attrs.item); 26 | scope.list = scope.$eval(attrs.list); 27 | scope.list.push(angular.copy(scope.item)); 28 | $rootScope.$emit('cms.elementsChanged') 29 | $rootScope.$emit('cms.updateView', true); 30 | scope.item = {}; 31 | }); 32 | } 33 | } 34 | 35 | }); 36 | -------------------------------------------------------------------------------- /public/app/components/mb-list-add/mb-list-add.styl: -------------------------------------------------------------------------------- 1 | // .add-to-list-btn { 2 | // position: absolute; 3 | // top: -20px; 4 | // right: -10px; 5 | // padding-top: 5px; 6 | // padding-left: 10px; 7 | // width: 35px; 8 | // height: 30px; 9 | // border-radius: 3px; 10 | // background-color: #2ECC71; 11 | // color: #ECF0F1; 12 | // cursor: pointer; 13 | // z-index: 2; 14 | // } 15 | 16 | .add-to-list-btn 17 | box-sizing: border-box 18 | background-color: white 19 | // border-radius: 100px 20 | padding 1em 1.2em 0.9em 21 | position relative 22 | cursor pointer 23 | text-align: center 24 | color grey 25 | 26 | i 27 | color grey 28 | font-size: 1.5em 29 | // position: absolute 30 | // top 50% 31 | // left 50% 32 | // -webkit-transform: translate(-50%, -50%) 33 | // transform: translate(-50%, -50%) 34 | // font-size: 2.5em 35 | // color grey 36 | 37 | .add-to-list-btn:hover 38 | color #2ECC71 39 | i 40 | color #2ECC71 41 | -------------------------------------------------------------------------------- /public/app/components/mb-list-area/mb-list-area-directive.js: -------------------------------------------------------------------------------- 1 | angular.module('meanbaseApp') 2 | .directive('mbListArea', function ($rootScope) { 3 | return { 4 | templateUrl: require('./mb-list-area.jade'), 5 | restrict: 'A', 6 | scope: true, 7 | link: function (scope, element, attrs) { 8 | if(attrs.mbListArea) { 9 | scope.areaName = attrs.mbListArea || 'list1'; 10 | if(!$rootScope.page.lists[scope.areaName]) { 11 | $rootScope.page.lists[scope.areaName] = []; 12 | } 13 | 14 | if(!$rootScope.currentUser) { return false } 15 | } 16 | } 17 | }; 18 | }); 19 | -------------------------------------------------------------------------------- /public/app/components/mb-list-area/mb-list-area.directive.spec.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingfriend1/meanbase/6639e49f9c7eae7cdf5b4a1c7ff17f8ed9c0215f/public/app/components/mb-list-area/mb-list-area.directive.spec.js -------------------------------------------------------------------------------- /public/app/components/mb-list-area/mb-list-area.jade: -------------------------------------------------------------------------------- 1 | div(ng-sortable="sortableLists" ng-class="{'mb-drag-to-room': $root.editMode && page.lists[areaName].length === 0}") 2 | div(ng-repeat="listItem in page.lists[areaName]" mb-dynamic-html="listItem.html" class="relative" ng-class="{'mb-draggable': $root.editMode}") 3 | //- mb-list-remove(list="page.lists[areaName]" item="listItem") 4 | //- div.mb-drag-handle(ng-if="$root.editMode") 5 | //- i.fa.fa-arrows 6 | mb-extension-edit(item="listItem") 7 | div(mb-list-selector="{{areaName}}") 8 | -------------------------------------------------------------------------------- /public/app/components/mb-list-area/mb-list-area.styl: -------------------------------------------------------------------------------- 1 | .mb-drag-to-room 2 | margin 0 0.5em 0.75em 3 | padding 1.5em 1em 0.5em 4 | background-color CLOUDS 5 | border 2px dashed SILVER 6 | 7 | .mb-drag-handle 8 | position: absolute 9 | top: -20px 10 | right: 32px 11 | padding-top: 5px 12 | padding-left: 10px 13 | width: 35px 14 | height: 30px 15 | border-radius: 3px 16 | background-color: #95A5A6 17 | color: #ECF0F1 18 | cursor move 19 | i 20 | cursor move 21 | -------------------------------------------------------------------------------- /public/app/components/mb-list-remove/mb-list-remove.directive.js: -------------------------------------------------------------------------------- 1 | angular.module('meanbaseApp') 2 | .directive('mbListRemove', function ($rootScope, endpoints, $timeout, api) { 3 | return { 4 | template: '
', 5 | restrict: 'EA', 6 | scope: true, 7 | link: function (scope, element, attrs) { 8 | 9 | if(!$rootScope.isLoggedIn) { return false; } 10 | 11 | var list = scope.$parent.$eval(attrs.list); 12 | var item = scope.$parent.$eval(attrs.item); 13 | 14 | element.bind('click', function() { 15 | $timeout(function() { 16 | var index = list.indexOf(item); 17 | if(index !== -1) { 18 | list.splice(index, 1); 19 | 20 | api.custom.delete({belongsTo: item.label, key: item.key}) 21 | $rootScope.$emit('cms.elementsChanged') 22 | } 23 | }); 24 | 25 | }); 26 | } 27 | } 28 | 29 | }); 30 | -------------------------------------------------------------------------------- /public/app/components/mb-list-remove/mb-list-remove.styl: -------------------------------------------------------------------------------- 1 | .remove-from-list-btn { 2 | position: absolute; 3 | top: -20px; 4 | right: -10px; 5 | padding-top: 2px; 6 | padding-left: 2px; 7 | width: 35px; 8 | height: 30px; 9 | border-radius: 3px; 10 | background-color: #E74C3C; 11 | color: #ECF0F1; 12 | cursor: pointer; 13 | z-index: 2; 14 | } 15 | -------------------------------------------------------------------------------- /public/app/components/mb-list-selector/mb-list-selector.jade: -------------------------------------------------------------------------------- 1 | .btn.btn-success.btn-block(ng-click="openModal()" ng-if="$root.editMode").mb-list-selector-btn 2 | i.fa.fa-plus.fa-lg 3 | //- span Add-ons 4 | -------------------------------------------------------------------------------- /public/app/components/mb-list-selector/mb-list-selector.styl: -------------------------------------------------------------------------------- 1 | mb-list-selector 2 | display inline-block 3 | 4 | mb-list-selector:focus 5 | outline none 6 | 7 | .mb-list-selector-btn:focus 8 | outline none 9 | .mb-list-selector-btn 10 | position relative 11 | display inline-block 12 | outline none 13 | border-radius: 99em 14 | margin: 1em 15 | width: 40px 16 | height 40px 17 | background-color: white 18 | color grey 19 | border 2px solid grey 20 | 21 | i 22 | position: absolute 23 | top 50% 24 | left 50% 25 | -webkit-transform translate(-50%, -50%) 26 | transform translate(-50%, -50%) 27 | 28 | .extensions-selector 29 | .nav-tabs 30 | cursor pointer 31 | .form-group 32 | margin-top 1em 33 | .extensions-list 34 | max-height 300px 35 | overflow auto 36 | .list-item 37 | position relative 38 | overflow hidden 39 | // background-color PETER-RIVER 40 | padding 0.3em 41 | margin 0.5em 0 42 | border-bottom 2px solid CLOUDS 43 | cursor pointer 44 | .list-image 45 | display inline-block 46 | vertical-align middle 47 | max-width 200px 48 | .checkbox 49 | position absolute 50 | top 50% 51 | right 1em 52 | margin 0 53 | transform: translateY(-50%); 54 | display inline-block 55 | color MIDNIGHT-BLUE 56 | font-size 1.5em 57 | .list-title 58 | display inline-block 59 | vertical-align middle 60 | margin 0 0 61 | color MIDNIGHT-BLUE 62 | margin 0.3em 63 | .choose-extensions 64 | margin 1em 0 65 | -------------------------------------------------------------------------------- /public/app/components/mb-list-selector/mb-list.modal.jade: -------------------------------------------------------------------------------- 1 | #list-modal.list-selector 2 | .modal-header 3 | button.close(ng-click="$dismiss()") 4 | span(aria-hidden="true") × 5 | span.sr-only Close 6 | h4.modal-title 7 | i.fa.fa-plus.fa-lg| 8 | | Choose an Add-on 9 | .modal-body 10 | .form-group 11 | .input-group 12 | .input-group-addon 13 | i.fa.fa-search 14 | input.form-control(type="text" placeholder="filter add-ons" ng-model="searchLists") 15 | .scrollable-body 16 | div(ng-repeat="item in $root.extensionOptions | filter:searchLists" ng-click="toggleSelected(item)").mb-list-item 17 | span.mb-list-label {{item.label}} 18 | i.fa.mb-list-checkbox(ng-class="{'fa-square-o': !item.selected, 'fa-check': item.selected}") 19 | hr 20 | div.text-center 21 | label Do you want to sync this add-on data with a group? 22 | .checkbox 23 | label 24 | input(type="checkbox" name="sync" ng-model="sync") 25 | | Sync and auto-fill Add-on 26 | .form-group(ng-show="sync") 27 | label with group: 28 | select(ng-options="group as group.key for group in syncGroups" ng-model="syncGroup").form-control 29 | 30 | button.btn.btn-info(type='button', data-toggle='collapse', data-target='#create-new-group') Create new group 31 | #create-new-group.collapse 32 | .form-group 33 | label What will the new group be called? 34 | input.form-control(ng-model="newSyncGroup") 35 | .modal-footer 36 | button.btn.btn-success(type='button' ng-click="chooseAddon(syncGroup, newSyncGroup, sync)") Choose 37 | button.btn.btn-default(type='button' ng-click="$dismiss()") Close 38 | -------------------------------------------------------------------------------- /public/app/components/mb-recaptcha/mb-recaptcha.directive.js: -------------------------------------------------------------------------------- 1 | // Handles starting up and shutting down the inline text editors and syncing up changes with the model when edits are saved 2 | angular.module('meanbaseApp') 3 | .directive('mbRecaptcha', function ($sanitize, $rootScope) { 4 | return { 5 | restrict: 'E', 6 | template: '
', 7 | scope: { 8 | field: '=ngModel' 9 | }, 10 | link: function (scope, element, attrs, ctrl) { 11 | scope.recaptchaClientKey = window.meanbaseGlobals.recaptchaClientKey; 12 | 13 | scope.setResponse = function(response) { 14 | scope.field = response; 15 | } 16 | } 17 | }; 18 | }); 19 | -------------------------------------------------------------------------------- /public/app/components/mb-text/mb-text.styl: -------------------------------------------------------------------------------- 1 | .medium-toolbar-arrow-under:after { 2 | top: 40px; 3 | } 4 | 5 | .medium-editor-toolbar li button { 6 | padding: 12px; 7 | } 8 | 9 | .medium-editor-toolbar-actions button.medium-editor-action { 10 | min-width: 40px; 11 | height: 40px; 12 | } 13 | 14 | .medium-editor-toolbar { 15 | max-width: 330px; 16 | border-top: 5px solid black; 17 | } 18 | 19 | .medium-editor-toolbar-active { 20 | visibility: visible; 21 | } 22 | 23 | .medium-editor-placeholder:after { 24 | position: static; 25 | color: black; 26 | } 27 | 28 | .medium-editor-insert-plugin:before { 29 | clear: none; 30 | display: none; 31 | content: ""; 32 | } 33 | 34 | .medium-editor-toolbar li button { 35 | border-right: none; 36 | } 37 | 38 | .medium-editor-insert-buttons .medium-editor-insert-buttons-addons li { 39 | border-radius: 10em; 40 | // padding: 0.4em 0; 41 | } 42 | 43 | .medium-insert-images.medium-insert-images-left, 44 | .medium-insert-images.medium-insert-images-right, 45 | .medium-insert-images-left.mediumInsert, 46 | .medium-insert-images-rightm.mediumInsert { 47 | max-width: 50%; 48 | } 49 | -------------------------------------------------------------------------------- /public/app/components/meanbase-editable/icons-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingfriend1/meanbase/6639e49f9c7eae7cdf5b4a1c7ff17f8ed9c0215f/public/app/components/meanbase-editable/icons-2x.png -------------------------------------------------------------------------------- /public/app/components/meanbase-editable/icons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingfriend1/meanbase/6639e49f9c7eae7cdf5b4a1c7ff17f8ed9c0215f/public/app/components/meanbase-editable/icons.png -------------------------------------------------------------------------------- /public/app/components/meanbase-editable/meanbase-editable.directive.spec.js: -------------------------------------------------------------------------------- 1 | describe('Directive: meanbase-editable', function () { 2 | 3 | // load the directive's module 4 | beforeEach(module('meanbaseApp')); 5 | 6 | var element, 7 | scope; 8 | 9 | beforeEach(inject(function ($rootScope) { 10 | scope = $rootScope.$new(); 11 | })); 12 | 13 | it('should make hidden element visible', inject(function ($compile) { 14 | element = angular.element(''); 15 | element = $compile(element)(scope); 16 | expect(element.text()).toBe('this is the meanbase-editable directive'); 17 | })); 18 | }); 19 | -------------------------------------------------------------------------------- /public/app/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingfriend1/meanbase/6639e49f9c7eae7cdf5b4a1c7ff17f8ed9c0215f/public/app/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /public/app/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingfriend1/meanbase/6639e49f9c7eae7cdf5b4a1c7ff17f8ed9c0215f/public/app/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /public/app/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingfriend1/meanbase/6639e49f9c7eae7cdf5b4a1c7ff17f8ed9c0215f/public/app/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /public/app/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingfriend1/meanbase/6639e49f9c7eae7cdf5b4a1c7ff17f8ed9c0215f/public/app/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /public/app/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingfriend1/meanbase/6639e49f9c7eae7cdf5b4a1c7ff17f8ed9c0215f/public/app/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /public/app/robots.txt: -------------------------------------------------------------------------------- 1 | # robotstxt.org 2 | 3 | User-agent: * 4 | -------------------------------------------------------------------------------- /public/assets/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore everything in this directory 2 | * 3 | # Except this file 4 | !.gitignore 5 | -------------------------------------------------------------------------------- /public/extensions/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore everything 2 | * 3 | # But not these files... 4 | !.gitignore 5 | !md-bullet-point-list/** 6 | !md-bullet-point-list 7 | 8 | !md-selling-point-list/** 9 | !md-selling-point-list 10 | 11 | !mb-search/** 12 | !mb-search 13 | 14 | !mb-subscribe/** 15 | !mb-subscribe 16 | 17 | .DS_Store 18 | -------------------------------------------------------------------------------- /public/extensions/mb-search/index.js: -------------------------------------------------------------------------------- 1 | // inject jade 2 | import "./mb-search-box-extension.html"; 3 | import "./search-form.html"; 4 | // end inject jade 5 | 6 | // inject js 7 | import "./search-form.directive.js"; 8 | // end inject js 9 | 10 | // inject stylus 11 | import "./search-form.styl"; 12 | // end inject stylus 13 | 14 | // It works 15 | -------------------------------------------------------------------------------- /public/extensions/mb-search/mb-search-box-extension.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /public/extensions/mb-search/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingfriend1/meanbase/6639e49f9c7eae7cdf5b4a1c7ff17f8ed9c0215f/public/extensions/mb-search/screenshot.png -------------------------------------------------------------------------------- /public/extensions/mb-search/search-form.directive.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // This directive uses the slug passed in to get the appropriate images and display them in a slider 4 | 5 | angular.module('extensions') 6 | .directive('searchForm', function (endpoints, $rootScope, helpers, api) { 7 | return { 8 | templateUrl: require('./search-form.html'), 9 | restrict: 'EA', 10 | link: function (scope, element, attrs) { 11 | scope.searchString = ''; 12 | scope.search = function() { 13 | api.pages.find({ $text: { $search: scope.searchString} }).then(function(response) { 14 | scope.results = !_.isEmpty(response)? response: [{url: '', title: 'No results'}]; 15 | }); 16 | }; 17 | } 18 | }; 19 | }); 20 | -------------------------------------------------------------------------------- /public/extensions/mb-search/search-form.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 | 6 |
7 | 8 |
9 |
10 |
11 |
Pages with "{{searchString}}"
12 |
13 | {{result.title | htmlToPlainText}} 14 |
15 |
16 |
17 | -------------------------------------------------------------------------------- /public/extensions/mb-search/search-form.styl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingfriend1/meanbase/6639e49f9c7eae7cdf5b4a1c7ff17f8ed9c0215f/public/extensions/mb-search/search-form.styl -------------------------------------------------------------------------------- /public/extensions/mb-subscribe/index.js: -------------------------------------------------------------------------------- 1 | // inject jade 2 | import "./subscribe-form-extension.jade"; 3 | // end inject jade 4 | 5 | // inject js 6 | // end inject js 7 | 8 | // inject stylus 9 | // end inject stylus 10 | -------------------------------------------------------------------------------- /public/extensions/mb-subscribe/subscribe-form-extension.jade: -------------------------------------------------------------------------------- 1 | .well 2 | .input-group 3 | input#email.btn.btn-lg(type='email', placeholder='Your Email', required='true' ng-model="subscribeEmail") 4 | button.btn.btn-info.btn-lg(ng-click="subscribeEmail? $root.subscribe(subscribeEmail): null") Subscribe 5 | //- button.btn.btn-info.btn-lg(ng-click="subscribeEmail? $root.unsubscribe(subscribeEmail): null") Subscribe 6 | -------------------------------------------------------------------------------- /public/extensions/md-bullet-point-list/bullet-point-list-extension.html: -------------------------------------------------------------------------------- 1 |
2 | 21 |
22 | -------------------------------------------------------------------------------- /public/extensions/md-bullet-point-list/index.js: -------------------------------------------------------------------------------- 1 | // inject jade 2 | import "./bullet-point-list-extension.html"; 3 | // end inject jade 4 | 5 | // inject js 6 | // end inject js 7 | 8 | // inject stylus 9 | // end inject stylus 10 | 11 | // It works 12 | -------------------------------------------------------------------------------- /public/extensions/md-selling-point-list/index.js: -------------------------------------------------------------------------------- 1 | // inject jade 2 | import "./selling-point-extension.html"; 3 | import "./selling-point-list.html"; 4 | import "./toggle-type.modal.html"; 5 | // end inject jade 6 | 7 | // inject js 8 | import "./selling-point-list.directive.js"; 9 | // end inject js 10 | 11 | // inject stylus 12 | import "./selling-point-list.styl"; 13 | // end inject stylus 14 | -------------------------------------------------------------------------------- /public/extensions/md-selling-point-list/selling-point-extension.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /public/extensions/md-selling-point-list/selling-point-list.directive.js: -------------------------------------------------------------------------------- 1 | angular.module('meanbaseApp') 2 | .directive('sellingPointList', function ($rootScope, endpoints, $compile, $timeout, $modal) { 3 | return { 4 | templateUrl: require('./selling-point-list.html'), 5 | restrict: 'EA', 6 | replace: true, 7 | link: function (scope, element, attrs) { 8 | scope.point = {}; 9 | 10 | scope.openModal = function (point) { 11 | var modalInstance = $modal.open({ 12 | templateUrl: require('./toggle-type.modal.html'), 13 | controller: function menuModal($scope, $modalInstance, point) { 14 | if(!point.left) { point.left = false; } 15 | $scope.left = point.left.toString(); 16 | $scope.chooseLeft = function(left) { 17 | point.left = left === 'true'; 18 | $rootScope.$emit('cms.elementsChanged') 19 | $modalInstance.dismiss(); 20 | }; 21 | }, 22 | resolve: { 23 | point: function() { 24 | return point; 25 | } 26 | }, 27 | size: 'md' 28 | }); 29 | }; 30 | } 31 | } 32 | }); 33 | -------------------------------------------------------------------------------- /public/extensions/md-selling-point-list/selling-point-list.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | 14 |
15 | 16 |
17 |
18 |
19 |
20 |
21 | 22 |
23 | -------------------------------------------------------------------------------- /public/extensions/md-selling-point-list/selling-point-list.styl: -------------------------------------------------------------------------------- 1 | .md-toggle-type { 2 | position: absolute; 3 | top: -20px; 4 | right: 32px; 5 | padding-top: 5px; 6 | padding-left: 10px; 7 | width: 35px; 8 | height: 30px; 9 | border-radius: 3px; 10 | background-color: #95A5A6; 11 | color: #ECF0F1; 12 | cursor: pointer; 13 | z-index: 2; 14 | } 15 | -------------------------------------------------------------------------------- /public/extensions/md-selling-point-list/toggle-type.modal.html: -------------------------------------------------------------------------------- 1 |
2 | 6 | 15 | 19 |
20 | -------------------------------------------------------------------------------- /public/shared/api/api.service.js: -------------------------------------------------------------------------------- 1 | angular.module('meanbaseApp') 2 | .service('api', function (endpoints) { 3 | 4 | var api = { 5 | publishedPages: new endpoints('pages'), 6 | custom: new endpoints('custom'), 7 | searchPages: new endpoints('pages'), 8 | searchPublishedPages: new endpoints('pages'), 9 | pages: new endpoints('pages'), 10 | approvedComments: new endpoints('comments'), 11 | comments: new endpoints('comments'), 12 | bannedMembers: new endpoints('bans'), 13 | publishedMenus: new endpoints('menus'), 14 | menus: new endpoints('menus'), 15 | sharedContent: new endpoints("shared-content"), 16 | extensions: new endpoints('extensions'), 17 | themes: new endpoints('themes'), 18 | media: new endpoints('images'), 19 | settings: new endpoints('settings'), 20 | import: new endpoints('import'), 21 | developmentMode: new endpoints('development-mode'), 22 | roles: new endpoints('roles'), 23 | users: new endpoints('users'), 24 | staging: new endpoints('staging'), 25 | importExport: new endpoints('import-export'), 26 | subscribe: new endpoints('subscribe') 27 | }; 28 | 29 | return api; 30 | }); 31 | -------------------------------------------------------------------------------- /public/shared/api/api.service.spec.js: -------------------------------------------------------------------------------- 1 | describe('Service: api', function () { 2 | 3 | // load the service's module 4 | beforeEach(module('meanbaseApp')); 5 | 6 | // instantiate service 7 | var api; 8 | beforeEach(inject(function (_api_) { 9 | api = _api_; 10 | })); 11 | 12 | it('should do something', function () { 13 | expect(!!api).toBe(true); 14 | }); 15 | 16 | }); 17 | -------------------------------------------------------------------------------- /public/shared/doubleClick/doubleClick.directive.js: -------------------------------------------------------------------------------- 1 | angular.module('meanbaseApp') 2 | .directive('doubleClick', function($timeout) { 3 | 4 | var CLICK_DELAY = 250 5 | var $ = angular.element 6 | 7 | return { 8 | priority: 1, // run before event directives 9 | restrict: 'A', 10 | link: function(scope, element, attrs) { 11 | var clickCount = 0 12 | var clickTimeout 13 | 14 | function doubleClick(e) { 15 | e.preventDefault() 16 | e.stopImmediatePropagation() 17 | $timeout.cancel(clickTimeout) 18 | clickCount = 0 19 | scope.$apply(function() { scope.$eval(attrs.doubleClick) }) 20 | } 21 | 22 | function singleClick(clonedEvent) { 23 | clickCount = 0 24 | if (attrs.ngClick) scope.$apply(function() { scope.$eval(attrs.ngClick) }) 25 | if (clonedEvent) element.trigger(clonedEvent) 26 | } 27 | 28 | function delaySingleClick(e) { 29 | var clonedEvent = $.Event('click', e) 30 | clonedEvent._delayedSingleClick = true 31 | e.preventDefault() 32 | e.stopImmediatePropagation() 33 | clickTimeout = $timeout(singleClick.bind(null, clonedEvent), CLICK_DELAY) 34 | } 35 | 36 | element.bind('click', function(e) { 37 | if (e._delayedSingleClick) return 38 | if (clickCount++) doubleClick(e) 39 | else delaySingleClick(e) 40 | }) 41 | 42 | } 43 | } 44 | 45 | }) 46 | -------------------------------------------------------------------------------- /public/shared/doubleClick/doubleClick.directive.spec.js: -------------------------------------------------------------------------------- 1 | describe('Directive: doubleClick', function () { 2 | 3 | // load the directive's module 4 | beforeEach(module('meanbaseApp')); 5 | 6 | var element, 7 | scope; 8 | 9 | beforeEach(inject(function ($rootScope) { 10 | scope = $rootScope.$new(); 11 | })); 12 | 13 | it('should make hidden element visible', inject(function ($compile) { 14 | element = angular.element(''); 15 | element = $compile(element)(scope); 16 | expect(element.text()).toBe('this is the doubleClick directive'); 17 | })); 18 | }); 19 | -------------------------------------------------------------------------------- /public/shared/endpoints/endpoints.service.spec.js: -------------------------------------------------------------------------------- 1 | describe('Service: endpoints', function () { 2 | 3 | // load the service's module 4 | beforeEach(module('meanbaseApp')); 5 | 6 | // instantiate service 7 | var endpoints; 8 | beforeEach(inject(function (_endpoints_) { 9 | endpoints = _endpoints_; 10 | })); 11 | 12 | it('should do something', function () { 13 | expect(!!endpoints).toBe(true); 14 | }); 15 | 16 | }); 17 | -------------------------------------------------------------------------------- /public/shared/fallback-src/fallback-src.directive.js: -------------------------------------------------------------------------------- 1 | angular.module('meanbaseApp') 2 | .directive('fallbackSrc', function () { 3 | return { 4 | template: '
', 5 | restrict: 'EA', 6 | link: function (scope, element, attrs) { 7 | if(attrs.fallbackSrc) { 8 | element.bind('error', function() { 9 | angular.element(this).attr("src", attrs.fallbackSrc); 10 | }); 11 | } 12 | } 13 | }; 14 | }); 15 | -------------------------------------------------------------------------------- /public/shared/fallback-src/fallback-src.directive.spec.js: -------------------------------------------------------------------------------- 1 | describe('Directive: fallbackSrc', function () { 2 | 3 | // load the directive's module 4 | beforeEach(module('meanbaseApp')); 5 | 6 | var element, 7 | scope; 8 | 9 | beforeEach(inject(function ($rootScope) { 10 | scope = $rootScope.$new(); 11 | })); 12 | 13 | it('should make hidden element visible', inject(function ($compile) { 14 | element = angular.element(''); 15 | element = $compile(element)(scope); 16 | expect(element.text()).toBe('this is the fallbackSrc directive'); 17 | })); 18 | }); 19 | -------------------------------------------------------------------------------- /public/shared/feathers/feathers.service.js: -------------------------------------------------------------------------------- 1 | const feathers = require('feathers/client') 2 | const socketio = require('feathers-socketio/client'); 3 | const hooks = require('feathers-hooks'); 4 | const io = require('socket.io-client'); 5 | const localstorage = require('feathers-localstorage'); 6 | const authentication = require('feathers-authentication/client'); 7 | const rest = require('feathers-rest/client'); 8 | 9 | angular.module('meanbaseApp').factory('feathers', function() { 10 | // const socket = io(window.location.origin); 11 | 12 | window.app = feathers() 13 | .configure(hooks()) 14 | .configure(rest(window.location.origin).jquery(jQuery)) 15 | // .configure(socketio(socket)) 16 | // .configure(rest(window.location.origin).fetch(window.fetch.bind(window))) 17 | .configure(authentication({ storage: window.localStorage, localEndpoint: '/api/auth/local', tokenEndpoint: '/api/auth/token' })); 18 | 19 | return app; 20 | }); 21 | -------------------------------------------------------------------------------- /public/shared/helpers/helpers.service.spec.js: -------------------------------------------------------------------------------- 1 | describe('Service: helpers', function () { 2 | 3 | // load the service's module 4 | beforeEach(module('meanbaseApp')); 5 | 6 | // instantiate service 7 | var helpers; 8 | beforeEach(inject(function (_helpers_) { 9 | helpers = _helpers_; 10 | })); 11 | 12 | it('should do something', function () { 13 | expect(!!helpers).toBe(true); 14 | }); 15 | 16 | }); 17 | -------------------------------------------------------------------------------- /public/shared/htmlToPlainText/htmlToPlainText.filter.js: -------------------------------------------------------------------------------- 1 | angular.module('meanbaseApp'). 2 | filter('htmlToPlainText', function() { 3 | return function(text) { 4 | return text ? String(text).replace(/<[^>]+>/gm, '') : ''; 5 | }; 6 | } 7 | ); 8 | -------------------------------------------------------------------------------- /public/shared/image-selector/image-selector.directive.spec.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | describe('Directive: imageSelector', function () { 4 | 5 | // load the directive's module and view 6 | beforeEach(module('meanbaseApp')); 7 | beforeEach(module('components/image-selector/image-selector.html')); 8 | 9 | var element, scope; 10 | 11 | beforeEach(inject(function ($rootScope) { 12 | scope = $rootScope.$new(); 13 | })); 14 | 15 | it('should make hidden element visible', inject(function ($compile) { 16 | element = angular.element(''); 17 | element = $compile(element)(scope); 18 | scope.$apply(); 19 | expect(element.text()).toBe('this is the imageSelector directive'); 20 | })); 21 | }); 22 | -------------------------------------------------------------------------------- /public/shared/mb-animate/mb-animate.directive.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | angular.module('meanbaseApp') 4 | .directive('mbAnimate', function ($rootScope) { 5 | return { 6 | template: '
', 7 | restrict: 'EA', 8 | link: function (scope, element, attrs) { 9 | if(!$rootScope.isLoggedIn) { return false; } 10 | element.addClass('animated'); 11 | if(attrs.mbAnimationTrigger) { 12 | scope.$watch(function() { 13 | return attrs.mbAnimationTrigger; 14 | }, function(nv, ov) { 15 | if(nv === ov) { return false; } 16 | if(nv === 'true' || nv === true) { 17 | if(attrs.mbAnimate) { 18 | element.addClass(attrs.mbAnimate); 19 | setTimeout(function() { 20 | element.removeClass(attrs.mbAnimate); 21 | }, 2000); 22 | } 23 | if(attrs.mbDeAnimate) { 24 | element.removeClass(attrs.mbDeAnimate); 25 | } 26 | } else { 27 | if(attrs.mbDeAnimate) { 28 | element.addClass(attrs.mbDeAnimate); 29 | setTimeout(function() { 30 | element.removeClass(attrs.mbDeAnimate); 31 | }, 2000); 32 | } 33 | element.removeClass(attrs.mbAnimate); 34 | } 35 | }); 36 | } 37 | } 38 | }; 39 | }); 40 | -------------------------------------------------------------------------------- /public/shared/mb-animate/mb-animate.directive.spec.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | describe('Directive: mb-animate', function () { 4 | 5 | // load the directive's module 6 | beforeEach(module('meanbaseApp')); 7 | 8 | var element, 9 | scope; 10 | 11 | beforeEach(inject(function ($rootScope) { 12 | scope = $rootScope.$new(); 13 | })); 14 | 15 | it('should make hidden element visible', inject(function ($compile) { 16 | element = angular.element(''); 17 | element = $compile(element)(scope); 18 | expect(element.text()).toBe('this is the mb-animate directive'); 19 | })); 20 | }); 21 | -------------------------------------------------------------------------------- /public/shared/missing/missing.controller.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | angular.module('meanbaseApp') 4 | .controller('MissingCtrl', function ($scope) { 5 | 6 | }); 7 | -------------------------------------------------------------------------------- /public/shared/missing/missing.controller.spec.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | describe('Controller: MissingCtrl', function () { 4 | 5 | // load the controller's module 6 | beforeEach(module('meanbaseApp')); 7 | 8 | var MissingCtrl, scope; 9 | 10 | // Initialize the controller and a mock scope 11 | beforeEach(inject(function ($controller, $rootScope) { 12 | scope = $rootScope.$new(); 13 | MissingCtrl = $controller('MissingCtrl', { 14 | $scope: scope 15 | }); 16 | })); 17 | 18 | it('should ...', function () { 19 | expect(1).toEqual(1); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /public/shared/missing/missing.jade: -------------------------------------------------------------------------------- 1 | .container.meanbase-404 2 | h1 3 | | Not found 4 | span :( 5 | p Sorry, but the page you were trying to view does not exist. 6 | p It looks like this was the result of either: 7 | ul 8 | li a mistyped address 9 | li an out-of-date link 10 | .text-center 11 | a(href="/") Home 12 | -------------------------------------------------------------------------------- /public/shared/missing/missing.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | angular.module('meanbaseApp') 4 | .config(function ($stateProvider) { 5 | $stateProvider 6 | .state('main.missing', { 7 | url: '/missing', 8 | templateUrl: require('./missing.jade'), 9 | controller: 'MissingCtrl' 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /public/shared/missing/missing.styl: -------------------------------------------------------------------------------- 1 | .meanbase-404 2 | padding 30px 10px 3 | font-size 20px 4 | line-height 1.4 5 | color #737373 6 | background #f0f0f0 7 | -webkit-text-size-adjust 100% 8 | -ms-text-size-adjust 100% 9 | input 10 | font-family "Helvetica Neue", Helvetica, Arial, sans-serif 11 | 12 | -moz-selection 13 | background #b3d4fc 14 | text-shadow none 15 | 16 | selection 17 | background #b3d4fc 18 | text-shadow none 19 | 20 | max-width 500px 21 | _width 500px 22 | padding 30px 20px 50px 23 | border 1px solid #b3b3b3 24 | border-radius 4px 25 | margin 4em auto 26 | box-shadow 0 1px 10px #a7a7a7, inset 0 1px 0 #fff 27 | background #fcfcfc 28 | 29 | h1 30 | margin 0 10px 31 | font-size 50px 32 | text-align center 33 | 34 | h1 span 35 | color #bbb 36 | 37 | h3 38 | margin 1.5em 0 0.5em 39 | 40 | p 41 | margin 1em 0 42 | 43 | ul 44 | padding 0 0 0 40px 45 | margin 1em 0 46 | 47 | .container 48 | max-width 380px 49 | _width 380px 50 | margin 0 auto 51 | -------------------------------------------------------------------------------- /public/shared/mongoose-error/mongoose-error.directive.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | /** 4 | * Removes server error when user updates input 5 | */ 6 | angular.module('meanbaseApp') 7 | .directive('mongooseError', function () { 8 | return { 9 | restrict: 'A', 10 | require: 'ngModel', 11 | link: function(scope, element, attrs, ngModel) { 12 | element.on('keydown', function() { 13 | return ngModel.$setValidity('mongoose', true); 14 | }); 15 | } 16 | }; 17 | }); 18 | -------------------------------------------------------------------------------- /public/shared/ng-enter/ng-enter.directive.js: -------------------------------------------------------------------------------- 1 | 2 | angular.module('meanbaseApp') 3 | .directive('ngEnter', function () { 4 | return function (scope, element, attrs) { 5 | element.bind("keydown keypress", function (event) { 6 | if(event.which === 13) { 7 | scope.$apply(function (){ 8 | scope.$eval(attrs.ngEnter); 9 | }); 10 | 11 | event.preventDefault(); 12 | } 13 | }); 14 | }; 15 | }); 16 | -------------------------------------------------------------------------------- /public/shared/ng-enter/ng-enter.directive.spec.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | describe('Directive: ngEnter', function () { 4 | 5 | // load the directive's module 6 | beforeEach(module('meanbaseApp')); 7 | 8 | var element, 9 | scope; 10 | 11 | beforeEach(inject(function ($rootScope) { 12 | scope = $rootScope.$new(); 13 | })); 14 | 15 | it('should make hidden element visible', inject(function ($compile) { 16 | element = angular.element(''); 17 | element = $compile(element)(scope); 18 | expect(element.text()).toBe('this is the ngEnter directive'); 19 | })); 20 | }); 21 | -------------------------------------------------------------------------------- /public/shared/sortable/sortable.directive.js: -------------------------------------------------------------------------------- 1 | 2 | angular.module('meanbaseApp') 3 | .directive('sortable', function ($rootScope) { 4 | return { 5 | // templateUrl: 'components/sortable/sortable.html', 6 | restrict: 'EA', 7 | link: function (scope, element, attrs) { 8 | if(!$rootScope.isLoggedIn) { return false; } 9 | 10 | var menu = element.scope().menu; 11 | var wobbling = false; 12 | scope.$watch('editMode', function(nv, ov) { 13 | if(nv) { 14 | element.addClass('mb-draggable'); 15 | element.addClass('wobble'); 16 | wobbling = true; 17 | setTimeout(function() { 18 | element.removeClass('wobble'); 19 | wobbling = false; 20 | }, 2000); 21 | 22 | } else { 23 | if(wobbling) { 24 | element.removeClass('wobble'); 25 | wobbling = false; 26 | } 27 | } 28 | }); 29 | 30 | if(menu && !menu.published) { 31 | element.addClass('unpublished-menu'); 32 | } 33 | 34 | element.bind('mouseover', function() { 35 | if($rootScope.editMode) { 36 | element.addClass('wobble'); 37 | wobbling = true; 38 | } 39 | }); 40 | 41 | element.bind('mouseout', function() { 42 | if(wobbling) { 43 | element.removeClass('wobble'); 44 | wobbling = false; 45 | } 46 | }); 47 | } 48 | }; 49 | }); 50 | -------------------------------------------------------------------------------- /public/shared/sortable/sortable.directive.spec.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | describe('Directive: sortable', function () { 4 | 5 | // load the directive's module and view 6 | beforeEach(module('meanbaseApp')); 7 | beforeEach(module('components/sortable/sortable.html')); 8 | 9 | var element, scope; 10 | 11 | beforeEach(inject(function ($rootScope) { 12 | scope = $rootScope.$new(); 13 | })); 14 | 15 | it('should make hidden element visible', inject(function ($compile) { 16 | element = angular.element(''); 17 | element = $compile(element)(scope); 18 | scope.$apply(); 19 | expect(element.text()).toBe('this is the sortable directive'); 20 | })); 21 | }); 22 | -------------------------------------------------------------------------------- /public/shared/sortable/sortable.jade: -------------------------------------------------------------------------------- 1 | div this is the sortable directive -------------------------------------------------------------------------------- /public/shared/sortable/sortable.styl: -------------------------------------------------------------------------------- 1 | .wobble 2 | -webkit-animation wiggle 0.4s ease infinite 3 | animation wiggle 0.4s ease infinite 4 | @-webkit-keyframes wiggle 5 | 0% 6 | -webkit-transform rotateZ(4deg) 7 | 50% 8 | -webkit-transform rotateZ(-4deg) 9 | 100% 10 | -webkit-transform rotateZ(4deg) 11 | @-moz-keyframes wiggle 12 | 0% 13 | -moz-transform rotateZ(4deg) 14 | 50% 15 | -moz-transform rotateZ(-4deg) 16 | 100% 17 | -moz-transform rotateZ(4deg) 18 | @-o-keyframes wiggle 19 | 0% 20 | -o-transform rotateZ(4deg) 21 | 50% 22 | -o-transform rotateZ(-4deg) 23 | 100% 24 | -o-transform rotateZ(4deg) 25 | @keyframes wiggle 26 | 0% 27 | transform rotateZ(4deg) 28 | 50% 29 | transform rotateZ(-4deg) 30 | 100% 31 | transform rotateZ(4deg) -------------------------------------------------------------------------------- /public/shared/taglist/taglist.directive.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | angular.module('meanbaseApp') 4 | .directive('taglist', function ($rootScope) { 5 | return { 6 | templateUrl: require('./taglist.jade'), 7 | restrict: 'EA', 8 | scope: { 9 | tags:"=ngModel" 10 | }, 11 | link: function (scope, element, attrs) { 12 | 13 | if(!$rootScope.isLoggedIn) { return false; } 14 | 15 | // Removes a template from the list 16 | scope.deleteTag = function(tag) { 17 | scope.tags.splice(scope.tags.indexOf(tag), 1); 18 | }; 19 | 20 | // Get the text input element 21 | var input = element.find('input'); 22 | 23 | input.bind("keydown keypress", function (event) { 24 | 25 | // Remove error class every time a key is pressed 26 | input[0].classList.remove('error'); 27 | 28 | // When enter or space is pressed 29 | if(event.which === 13 || event.which === 32) { 30 | 31 | // Perform some simple validation 32 | var re = new RegExp("[_a-zA-Z0-9\\-\\.]+"); 33 | 34 | if(re.test(input[0].value)) { 35 | 36 | // If input text passes validation then push it to the templates list 37 | scope.tags.push(input[0].value); 38 | input[0].value = ''; 39 | scope.$apply(); 40 | 41 | } else { 42 | // Otherwise add the error class 43 | input[0].classList.add('error'); 44 | } 45 | event.preventDefault(); 46 | 47 | } 48 | }); 49 | } 50 | }; 51 | }); 52 | -------------------------------------------------------------------------------- /public/shared/taglist/taglist.directive.spec.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | describe('Directive: taglist', function () { 4 | 5 | // load the directive's module and view 6 | beforeEach(module('meanbaseApp')); 7 | beforeEach(module('components/taglist/taglist.html')); 8 | 9 | var element, scope; 10 | 11 | beforeEach(inject(function ($rootScope) { 12 | scope = $rootScope.$new(); 13 | })); 14 | 15 | it('should make hidden element visible', inject(function ($compile) { 16 | element = angular.element(''); 17 | element = $compile(element)(scope); 18 | scope.$apply(); 19 | expect(element.text()).toBe('this is the taglist directive'); 20 | })); 21 | }); 22 | -------------------------------------------------------------------------------- /public/shared/taglist/taglist.jade: -------------------------------------------------------------------------------- 1 | .tag-list 2 | span.tag(ng-repeat="tag in tags" ng-click="deleteTag(tag)") {{tag}} 3 | span.remove 4 | input(type="Text") -------------------------------------------------------------------------------- /public/shared/taglist/taglist.styl: -------------------------------------------------------------------------------- 1 | // Thank you DesignModo Flat Ui for these styles 2 | // I modified some to my preference 3 | 4 | .tag-list 5 | display inline-block 6 | min-width 80% 7 | padding 6px 1px 1px 6px 8 | margin-bottom 18px 9 | font-size 0 10 | text-align left 11 | background-color #fff 12 | border 2px solid #1abc9c 13 | border-radius 6px 14 | 15 | input 16 | min-width 80px 17 | max-width inherit 18 | padding 0 19 | margin 0 20 | font-size 14px 21 | color #34495e 22 | vertical-align top 23 | background-color transparent 24 | border none 25 | outline 0 26 | box-shadow none 27 | 28 | &.error 29 | color #E74C3C 30 | .tag 31 | color #fff 32 | background-color #1abc9c 33 | position relative 34 | display inline-block 35 | padding 6px 21px 6px 8px 36 | margin 0 5px 5px 0 37 | overflow hidden 38 | font-size 13px 39 | line-height 15px 40 | color #7b8996 41 | vertical-align middle 42 | cursor pointer 43 | // background-color #ebedef 44 | border-radius 4px 45 | font-weight 700 46 | line-height 1 47 | color #fff 48 | white-space nowrap 49 | 50 | .remove 51 | position absolute 52 | top 0 53 | right 0 54 | bottom 0 55 | z-index 2 56 | width 100% 57 | padding 0 10px 0 0 58 | font-size 12px 59 | text-align right 60 | text-decoration none 61 | cursor pointer 62 | &:after 63 | line-height 27px 64 | font-family FontAwesome 65 | content "\f00d" 66 | color white -------------------------------------------------------------------------------- /public/shared/validate/validate.directive.spec.js: -------------------------------------------------------------------------------- 1 | 2 | describe('Directive: validate', function () { 3 | 4 | // load the directive's module and view 5 | beforeEach(module('meanbaseApp')); 6 | beforeEach(module('components/validate/validate.html')); 7 | 8 | var element, scope; 9 | 10 | beforeEach(inject(function ($rootScope) { 11 | scope = $rootScope.$new(); 12 | })); 13 | 14 | it('should make hidden element visible', inject(function ($compile) { 15 | element = angular.element(''); 16 | element = $compile(element)(scope); 17 | scope.$apply(); 18 | expect(element.text()).toBe('this is the validate directive'); 19 | })); 20 | }); 21 | -------------------------------------------------------------------------------- /public/shared/validate/validate.jade: -------------------------------------------------------------------------------- 1 | div 2 | ng-transclude 3 | div.help-block.required This field is required. 4 | div.help-block.error {{errorMessage}} -------------------------------------------------------------------------------- /public/shared/validate/validate.styl: -------------------------------------------------------------------------------- 1 | .has-error 2 | .help-block.error 3 | display block 4 | padding 6px 5 | .help-block.required 6 | display none 7 | 8 | .has-warning 9 | .help-block.required 10 | display block 11 | padding 6px 12 | .help-block.error 13 | display none 14 | 15 | .has-success 16 | .help-block 17 | display none 18 | padding 6px -------------------------------------------------------------------------------- /public/themes/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore everything 2 | * 3 | 4 | # But not these files... 5 | !.gitignore 6 | !meanbase-demo/** 7 | !meanbase-demo 8 | -------------------------------------------------------------------------------- /public/themes/meanbase-demo/LICENSE: -------------------------------------------------------------------------------- 1 | 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2013-2016 Blackrock Digital LLC. 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. -------------------------------------------------------------------------------- /public/themes/meanbase-demo/README.md: -------------------------------------------------------------------------------- 1 | # [Start Bootstrap](http://startbootstrap.com/) - [Landing Page](http://startbootstrap.com/template-overviews/landing-page/) 2 | 3 | [Landing Page](http://startbootstrap.com/template-overviews/landing-page/) is a multipurpose landing page template for [Bootstrap](http://getbootstrap.com/) created by [Start Bootstrap](http://startbootstrap.com/). 4 | 5 | ## Getting Started 6 | 7 | To begin using this template, choose one of the following options to get started: 8 | * [Download the latest release on Start Bootstrap](http://startbootstrap.com/template-overviews/landing-page/) 9 | * Clone the repo: `git clone https://github.com/BlackrockDigital/startbootstrap-landing-page.git` 10 | * Fork the repo 11 | 12 | ## Bugs and Issues 13 | 14 | Have a bug or an issue with this template? [Open a new issue](https://github.com/BlackrockDigital/startbootstrap-landing-page/issues) here on GitHub or leave a comment on the [template overview page at Start Bootstrap](http://startbootstrap.com/template-overviews/landing-page/). 15 | 16 | ## Creator 17 | 18 | Start Bootstrap was created by and is maintained by **[David Miller](http://davidmiller.io/)**, Owner of [Blackrock Digital](http://blackrockdigital.io/). 19 | 20 | * https://twitter.com/davidmillerskt 21 | * https://github.com/davidtmiller 22 | 23 | Start Bootstrap is based on the [Bootstrap](http://getbootstrap.com/) framework created by [Mark Otto](https://twitter.com/mdo) and [Jacob Thorton](https://twitter.com/fat). 24 | 25 | ## Copyright and License 26 | 27 | Copyright 2013-2016 Blackrock Digital LLC. Code released under the [MIT](https://github.com/BlackrockDigital/startbootstrap-landing-page/blob/gh-pages/LICENSE) license. -------------------------------------------------------------------------------- /public/themes/meanbase-demo/img/banner-bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingfriend1/meanbase/6639e49f9c7eae7cdf5b4a1c7ff17f8ed9c0215f/public/themes/meanbase-demo/img/banner-bg.jpg -------------------------------------------------------------------------------- /public/themes/meanbase-demo/img/dog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingfriend1/meanbase/6639e49f9c7eae7cdf5b4a1c7ff17f8ed9c0215f/public/themes/meanbase-demo/img/dog.png -------------------------------------------------------------------------------- /public/themes/meanbase-demo/img/intro-bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingfriend1/meanbase/6639e49f9c7eae7cdf5b4a1c7ff17f8ed9c0215f/public/themes/meanbase-demo/img/intro-bg.jpg -------------------------------------------------------------------------------- /public/themes/meanbase-demo/img/ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingfriend1/meanbase/6639e49f9c7eae7cdf5b4a1c7ff17f8ed9c0215f/public/themes/meanbase-demo/img/ipad.png -------------------------------------------------------------------------------- /public/themes/meanbase-demo/img/phones.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingfriend1/meanbase/6639e49f9c7eae7cdf5b4a1c7ff17f8ed9c0215f/public/themes/meanbase-demo/img/phones.png -------------------------------------------------------------------------------- /public/themes/meanbase-demo/index.js: -------------------------------------------------------------------------------- 1 | // inject jade 2 | import "./templates/blog/blog-template.html"; 3 | import "./templates/archive/archive-template.html"; 4 | import "./templates/home/home-template.html"; 5 | // end inject jade 6 | 7 | // inject js 8 | import "./templates/archive/archive.controller.js"; 9 | // end inject js 10 | 11 | // inject stylus 12 | import "./templates/archive/archive.styl"; 13 | import "./templates/blog/blog.styl"; 14 | import "./templates/blog/clean-blog.styl"; 15 | import "./css/landing-page.css"; 16 | // end inject stylus 17 | 18 | // it works 19 | -------------------------------------------------------------------------------- /public/themes/meanbase-demo/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingfriend1/meanbase/6639e49f9c7eae7cdf5b4a1c7ff17f8ed9c0215f/public/themes/meanbase-demo/screenshot.png -------------------------------------------------------------------------------- /public/themes/meanbase-demo/templates/archive/archive-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingfriend1/meanbase/6639e49f9c7eae7cdf5b4a1c7ff17f8ed9c0215f/public/themes/meanbase-demo/templates/archive/archive-screenshot.png -------------------------------------------------------------------------------- /public/themes/meanbase-demo/templates/archive/archive.controller.js: -------------------------------------------------------------------------------- 1 | angular.module('meanbaseApp') 2 | .controller('archiveCtrl', function ($scope, endpoints, api) { 3 | api.pages.find({ template: {$in: ['article', 'blog']} }).then(function(response) { 4 | $scope.posts = response; 5 | }); 6 | }); 7 | -------------------------------------------------------------------------------- /public/themes/meanbase-demo/templates/archive/archive.styl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingfriend1/meanbase/6639e49f9c7eae7cdf5b4a1c7ff17f8ed9c0215f/public/themes/meanbase-demo/templates/archive/archive.styl -------------------------------------------------------------------------------- /public/themes/meanbase-demo/templates/blog/blog-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingfriend1/meanbase/6639e49f9c7eae7cdf5b4a1c7ff17f8ed9c0215f/public/themes/meanbase-demo/templates/blog/blog-screenshot.png -------------------------------------------------------------------------------- /public/themes/meanbase-demo/templates/blog/blog.styl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingfriend1/meanbase/6639e49f9c7eae7cdf5b4a1c7ff17f8ed9c0215f/public/themes/meanbase-demo/templates/blog/blog.styl -------------------------------------------------------------------------------- /public/themes/meanbase-demo/templates/home/home-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingfriend1/meanbase/6639e49f9c7eae7cdf5b4a1c7ff17f8ed9c0215f/public/themes/meanbase-demo/templates/home/home-screenshot.png -------------------------------------------------------------------------------- /public/themes/meanbase-demo/theme.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Jon Paul", 3 | "email": "codingfriend1@gmail.com", 4 | "title": "Meanbase Demo", 5 | "description": "A theme to demo meanbase functionality" 6 | } -------------------------------------------------------------------------------- /src/app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // import 'babel-preset-es2017/polyfill'; 4 | 5 | import compileIndex from './components/compile-index'; 6 | const seed = require('./components/seed'); 7 | const path = require('path'); 8 | const serveStatic = require('feathers').static; 9 | const favicon = require('serve-favicon'); 10 | const compress = require('compression'); 11 | const cors = require('cors'); 12 | const feathers = require('feathers'); 13 | const configuration = require('feathers-configuration'); 14 | const hooks = require('feathers-hooks'); 15 | const rest = require('feathers-rest'); 16 | const bodyParser = require('body-parser'); 17 | const socketio = require('feathers-socketio'); 18 | const middleware = require('./middleware'); 19 | const services = require('./services'); 20 | const settings = require('./components/settings'); 21 | const routes = require('./routes'); 22 | 23 | const api = feathers(); 24 | 25 | api.configure(configuration(path.join(__dirname, '..'))); 26 | 27 | api.use(compress()) 28 | .options('*', cors()) 29 | .use(cors()) 30 | .use(bodyParser.json()) 31 | .use(bodyParser.urlencoded({ extended: true })) 32 | .configure(rest()) 33 | .configure(socketio()) 34 | .use(function(req, res, next) { 35 | req.feathers.ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress; 36 | next(); 37 | }) 38 | .configure(hooks()) 39 | .configure(settings) 40 | .configure(services) 41 | .configure(seed) 42 | .configure(compileIndex) 43 | .configure(middleware); 44 | 45 | const app = feathers() 46 | .configure(configuration(path.join(__dirname, '..'))) 47 | .configure(settings) 48 | .use('/api', api) 49 | .configure(routes) 50 | 51 | app.set('view engine', 'jade'); 52 | 53 | module.exports = app; 54 | -------------------------------------------------------------------------------- /src/components/patterns/index.js: -------------------------------------------------------------------------------- 1 | exports.isTitle = /^[A-Za-z0-9@:?&=.\/ _\-]*$/; 2 | 3 | exports.isURI = /(((http|https|ftp):\/\/([\w-\d]+\.)+[\w-\d]+){0,1}((\/|#)[\w~,;\-\.\/?%&+#=]*))/; 4 | 5 | exports.isFilePath = /^[0-9A-Za-z \/*_.\\\-]*$/; 6 | 7 | exports.isCSSClass = /^[A-Za-z0-9_ \-*]*$/; 8 | 9 | exports.isAnchorTarget = /^[_blank|_self|_parent|_top]*$/; 10 | 11 | exports.isText = /.*/; 12 | 13 | exports.isHTML = /.*/; 14 | -------------------------------------------------------------------------------- /src/components/seed/seed/comments.js: -------------------------------------------------------------------------------- 1 | var firstComment = { 2 | "author": "Admin", 3 | "url": "/why-cms", 4 | "content": "Comments can be added to articles.", 5 | "email": "admin@admin.com", 6 | "ip": "172.31.255.255", 7 | "approved": true, 8 | "likes": 2 9 | }; 10 | 11 | var secondComment = { 12 | "author": "test", 13 | "url": "/why-cms", 14 | "content": "I can approve or reject comments", 15 | "email": "test@test.com", 16 | "ip": "172.97.114.255", 17 | "approved": false, 18 | "likes": 0 19 | }; 20 | 21 | var thirdComment = { 22 | "email": "codingfriend@meanbase.com", 23 | "content": "You can manage comments on your site by approving or rejecting them. Google Recaptcha protects you from spam.", 24 | "url": "/why-cms", 25 | "approved": true, 26 | "date": "2016-08-28T02:05:02.842Z", 27 | "author": "Coding Friend", 28 | } 29 | 30 | module.exports = [firstComment, secondComment, thirdComment]; 31 | -------------------------------------------------------------------------------- /src/components/seed/seed/extensions.js: -------------------------------------------------------------------------------- 1 | const searchFolders = require('../../search-folders'); 2 | 3 | module.exports = async function() { 4 | 5 | const app = this; 6 | 7 | console.log('checking for extensions...'); 8 | 9 | const extensionjsons = await searchFolders.retrieveExtensions.call(this); 10 | 11 | // We are putting this on the global so that when the client/index.html is compiled it will include the extension links 12 | if(!global.meanbaseGlobals) { global.meanbaseGlobals = {}; } 13 | global.meanbaseGlobals.extensions = extensionjsons; 14 | 15 | if(extensionjsons.length === 0) { 16 | console.log('no extensions found'); 17 | return false; 18 | } 19 | 20 | try { 21 | await app.service('extensions').remove(null, {query: {}}); 22 | await app.service('extensions').create(extensionjsons); 23 | console.log('extensions initialized'); 24 | } catch (err) { 25 | console.log('Initializing extensions error: ', err); 26 | } 27 | }; 28 | -------------------------------------------------------------------------------- /src/components/seed/seed/menus.js: -------------------------------------------------------------------------------- 1 | var home = { 2 | "title": "Home", 3 | "url": "/", 4 | "position": 0, 5 | "group": "main" 6 | }; 7 | 8 | var tutorial = { 9 | "title": "Tutorial", 10 | "url": "/tutorial", 11 | "position": 0 12 | }; 13 | 14 | var login = { 15 | "title": "Login", 16 | "url": "/login", 17 | "group": "rightHand", 18 | "position": 1 19 | }; 20 | 21 | var youtubeDemo = { 22 | "title": "Youtube Demo", 23 | "url": "https://www.youtube.com/watch?v=tteztXru4eA&feature=youtu.be", 24 | "group": "sidebar", 25 | "position": 0 26 | }; 27 | 28 | var article = { 29 | "title": "Why a CMS?", 30 | "url": "/why-cms", 31 | "group": "main", 32 | "position": 2 33 | }; 34 | 35 | var login = { 36 | "title": "Login", 37 | "url": "/cms", 38 | "published": true, 39 | "target": "_self", 40 | "position": 4, 41 | "group": "main", 42 | }; 43 | 44 | module.exports = [login]; 45 | -------------------------------------------------------------------------------- /src/components/seed/seed/themes.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const searchFolders = require('../../search-folders'); 3 | 4 | module.exports = async function() { 5 | const app = this; 6 | 7 | console.log('Checking for themes...'); 8 | 9 | try { 10 | 11 | let activeTheme = await app.service('themes').find({ query: {active: true} }); 12 | 13 | if(!Array.isArray(activeTheme)) { activeTheme = [activeTheme]; } 14 | 15 | if(!activeTheme[0] || !activeTheme[0].url) { activeTheme[0] = {url: ''}; } 16 | 17 | let themejsons = await searchFolders.retrieveThemes.call(app, activeTheme[0].url); 18 | 19 | themejsons.forEach(theme => { 20 | console.log("Sucessfully grabbed theme: " + theme.title); 21 | }); 22 | 23 | await app.service('themes').remove(null, { query: {} }); 24 | 25 | await app.service('themes').create(themejsons); 26 | 27 | console.log('themes initialized'); 28 | 29 | } catch (err) { 30 | console.log("trouble fetching theme jsons: ", err); 31 | } 32 | 33 | }; 34 | -------------------------------------------------------------------------------- /src/components/seed/seed/user.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = function(app) { 3 | 4 | // let email = process.env.ADMIN_EMAIL 5 | // let pass = process.env.ADMIN_PASS 6 | // let name = process.env.ADMIN_NAME 7 | // if(email && pass && name) { 8 | // let admin = { 9 | // "email": email, 10 | // "password": pass, 11 | // "name": name, 12 | // "role": "admin" 13 | // } 14 | // return admin; 15 | // } 16 | 17 | return undefined 18 | }; 19 | -------------------------------------------------------------------------------- /src/components/settings/index.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const express = require('express'); 3 | 4 | module.exports = function() { 5 | const app = this; 6 | 7 | app.set('view engine', 'jade'); 8 | app.use( express.static(app.get('clientPath')) ); 9 | 10 | if(process.env.NODE_ENV !== 'production') { 11 | app.set('mongodb', process.env.DATABASE_URL || "mongodb://localhost:27017/meanbase-dev"); 12 | } 13 | 14 | }; 15 | -------------------------------------------------------------------------------- /src/hooks/allow-upsert.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | import errors from 'feathers-errors' 4 | 5 | export default options => { 6 | return hook => { 7 | if(hook.params) { 8 | hook.params = Object.assign({}, hook.params, {mongoose: {upsert: true} }) 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/hooks/attach-permissions.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | 3 | export default options => { 4 | return async hook => { 5 | try { 6 | let role; 7 | 8 | if(hook.params.user) { 9 | role = hook.params.user.role; 10 | } else if(hook.result) { 11 | role = hook.result.role; 12 | } 13 | 14 | if(!role) { return Promise.resolve(hook); } 15 | 16 | let access = await hook.app.service('roles').find({query: { role } }); 17 | 18 | let permissions; 19 | if(access.length > 0) { 20 | access = access[0]; 21 | permissions = _.keys(_.pickBy(access.permissions, value => value)); 22 | } else { 23 | permissions = []; 24 | } 25 | 26 | if(hook.params.user) { 27 | hook.params.user.permissions = permissions; 28 | } else if(hook.type === 'after' && hook.result && !Array.isArray(hook.result)) { 29 | hook.result.permissions = permissions; 30 | } 31 | 32 | return Promise.resolve(hook); 33 | } catch(err) { 34 | console.log("error attaching permissions: ", err); 35 | return Promise.reject(err); 36 | } 37 | } 38 | }; 39 | -------------------------------------------------------------------------------- /src/hooks/delete-custom-data.js: -------------------------------------------------------------------------------- 1 | import errors from 'feathers-errors' 2 | 3 | export default type => { 4 | return hook => { 5 | if (!hook.params.provider) { return hook; } 6 | 7 | if(!hook.result) { return hook; } 8 | 9 | let results = hook.result; 10 | if(!Array.isArray(hook.result)) { 11 | results = [hook.result]; 12 | } 13 | 14 | for (var i = 0; i < results.length; i++) { 15 | if(type === 'theme') { 16 | hook.app.service('custom').remove(null, { query: {belongsTo: results[i].title} }); 17 | } else if (type === 'extension') { 18 | hook.app.service('custom').remove(null, { query: {belongsTo: results[i].name} }); 19 | } 20 | } 21 | return hook; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/hooks/filter-by-permission-or-restrict.js: -------------------------------------------------------------------------------- 1 | import errors from 'feathers-errors' 2 | 3 | export default options => { 4 | return hook => { 5 | if (!hook.params.provider) { return hook; } 6 | 7 | if(hook.type !== 'after') { throw Error('Filter by Permission or Restrict must be an after hook'); } 8 | 9 | if(!hook.params.user || !hook.params.user.permissions || hook.params.user.permissions.length === 0) { 10 | if(Array.isArray(hook.result)) { 11 | hook.result = hook.result.map(function(item) { 12 | if(item.enabled) { return item; } else { return undefined; } 13 | }); 14 | } else { 15 | if(!hook.result.enabled) { 16 | hook.result = undefined; 17 | } 18 | } 19 | } 20 | 21 | if(hook.result) { 22 | if(Array.isArray(hook.result)) { 23 | hook.result = hook.result.map(function(item) { 24 | if(hook.params.user.permissions.indexOf(item.permission) > -1) { 25 | return item; 26 | } else if(item.enabled) { 27 | return item; 28 | } 29 | }); 30 | } else { 31 | if(hook.params.user.permissions.indexOf(hook.result.permission) === -1) { 32 | if(!hook.result.enabled) { 33 | hook.result = undefined; 34 | } 35 | } 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/hooks/has-permission-or-restrict.js: -------------------------------------------------------------------------------- 1 | import errors from 'feathers-errors'; 2 | 3 | export default (permissionName, restriction) => { 4 | return async (hook) => { 5 | 6 | if (!hook.params.provider) { return hook; } 7 | if(!hook.params.user || (hook.params.user.permissions.indexOf(permissionName) === -1 && hook.params.user.permissions.indexOf('allPrivilages') === -1) ) { 8 | 9 | let query = Object.assign({}, hook.params.query, restriction); 10 | 11 | const params = Object.assign({}, hook.params, { provider: undefined }); 12 | if(hook.id !== null && hook.id !== undefined) { 13 | const id = {}; 14 | id._id = hook.id; 15 | query = Object.assign(query, id); 16 | } 17 | try { 18 | let results = await this.find({ query }, params); 19 | 20 | if(hook.method === 'get' && Array.isArray(results) && results.length === 1) { 21 | hook.result = results[0]; 22 | return hook; 23 | } else { 24 | hook.result = results; 25 | return Promise.resolve(hook); 26 | } 27 | } catch (e) { 28 | return Promise.reject(new errors.NotFound(`No record found`)); 29 | } 30 | 31 | } 32 | }; 33 | }; 34 | -------------------------------------------------------------------------------- /src/hooks/has-permission.js: -------------------------------------------------------------------------------- 1 | export default permissionName => { 2 | return hook => { 3 | if (!hook.params.provider) { return hook; } 4 | if(!hook.params.user) { 5 | throw new Error('Cannot check permissions of a non-existant user.'); 6 | } else if (hook.params.user.permissions.indexOf(permissionName) === -1 && hook.params.user.permissions.indexOf('allPrivilages') === -1) { 7 | throw new Error('You must be a(n) ' + permissionName + ' to do that.'); 8 | } 9 | }; 10 | }; 11 | -------------------------------------------------------------------------------- /src/hooks/if-password-then-hash.js: -------------------------------------------------------------------------------- 1 | import errors from 'feathers-errors'; 2 | const auth = require('feathers-authentication').hooks; 3 | export default options => { 4 | return hook => { 5 | if (!hook.params.provider) { return hook; } 6 | 7 | if(hook.data.password) { 8 | return auth.hashPassword()(hook); 9 | } else { 10 | return hook; 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/hooks/is-enabled.js: -------------------------------------------------------------------------------- 1 | export default (options) => { 2 | return hook => { 3 | if (!hook.params.provider) { return hook; } 4 | if(!hook.params.user) { 5 | throw new Error('Cannot check enabled of a non-existant user.'); 6 | } else if(!hook.params.user.enabled) { 7 | throw new Error('Your account must be enabled to do that.'); 8 | } 9 | } 10 | }; 11 | -------------------------------------------------------------------------------- /src/hooks/is-target-enabled.js: -------------------------------------------------------------------------------- 1 | import errors from 'feathers-errors'; 2 | 3 | export default (options) => { 4 | return async hook => { 5 | 6 | if(!hook.params.provider) { return Promise.resolve(hook); } 7 | 8 | if(!hook.data.email) { 9 | return Promise.resolve(hook); 10 | } 11 | 12 | try { 13 | const found = await hook.app.service('users').find({query: {email: hook.data.email}}); 14 | if(found.length > 0) { 15 | if(found[0].enabled) { 16 | return Promise.resolve(hook); 17 | } else { 18 | return Promise.reject(new errors.Forbidden('This user is not enabled')); 19 | } 20 | } else { 21 | return Promise.reject(new errors.Forbidden('Could not find this user.')); 22 | } 23 | } catch(err) { 24 | console.log('Checking user enabled error', err); 25 | } 26 | 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/hooks/owner-or-restrict-changes.js: -------------------------------------------------------------------------------- 1 | import errors from 'feathers-errors'; 2 | 3 | export default options => { 4 | return hook => { 5 | if (!hook.params.provider) { return hook; } 6 | 7 | 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/hooks/permission-or-restrict-changes.js: -------------------------------------------------------------------------------- 1 | import errors from 'feathers-errors'; 2 | 3 | export default (permissionName, options) => { 4 | return (hook) => { 5 | if (!hook.params.provider) { return hook; } 6 | 7 | if(!hook.params.user || (hook.params.user.permissions.indexOf(permissionName) === -1 && hook.params.user.permissions.indexOf('allPrivilages') === -1) ) { 8 | for (var i = 0; i < options.restrictOn.length; i++) { 9 | if(hook.data[options.restrictOn[i]] !== undefined && hook.data[options.restrictOn[i]] !== null) { 10 | return Promise.reject(new errors.Forbidden('You are not permitted to update the ' + options.restrictOn[i] + ' field.')); 11 | } 12 | } 13 | return Promise.resolve(hook); 14 | } 15 | }; 16 | } 17 | -------------------------------------------------------------------------------- /src/hooks/recaptcha.js: -------------------------------------------------------------------------------- 1 | export default (options) => { 2 | return async (hook) => { 3 | try { 4 | Promise.resolve(hook); 5 | } catch(err) { 6 | Promise.reject(err); 7 | } 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /src/hooks/send-verification-email.js: -------------------------------------------------------------------------------- 1 | import errors from 'feathers-errors' 2 | 3 | export default options => { 4 | return hook => { 5 | 6 | if (!hook.params.provider) { return hook; } 7 | 8 | const user = hook.result 9 | 10 | if(process.env.EMAIL && hook.data && hook.data.email && user) { 11 | 12 | let link 13 | if(hook.app.get('port')) { 14 | link = `http://${hook.app.get('host')}:${hook.app.get('port')}/cms/account/verify/${user.verifyToken}` 15 | } else { 16 | link = `http://${hook.app.get('host')}/cms/account/verify/${user.verifyToken}` 17 | } 18 | 19 | let email = { 20 | from: process.env.EMAIL, 21 | to: hook.data.email, 22 | subject: 'Meanbase - Account Verification', 23 | html: `Dear ${user.name}, please click this link to verify your email address. ${link}` 24 | } 25 | 26 | hook.app.service('emails').create(email).then(function (result) { 27 | console.log('Sent email', result); 28 | }).catch(err => { 29 | console.log('Error sending email', err); 30 | }); 31 | } 32 | 33 | return hook 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const app = require('./app'); 4 | const port = app.get('port'); 5 | const server = app.listen(port); 6 | 7 | server.on('listening', () => 8 | console.log(`Feathers application started on ${app.get('host')}:${port}`) 9 | ); 10 | -------------------------------------------------------------------------------- /src/middleware/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const handler = require('feathers-errors/handler'); 4 | const notFound = require('./not-found-handler'); 5 | const logger = require('./logger'); 6 | 7 | module.exports = function() { 8 | // Add your custom middleware here. Remember, that 9 | // just like Express the order matters, so error 10 | // handling middleware should go last. 11 | const app = this; 12 | 13 | app.use(notFound()); 14 | app.use(logger(app)); 15 | app.use(handler()); 16 | }; 17 | -------------------------------------------------------------------------------- /src/middleware/logger.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const winston = require('winston'); 4 | 5 | module.exports = function(app) { 6 | // Add a logger to our app object for convenience 7 | app.logger = winston; 8 | 9 | return function(error, req, res, next) { 10 | if (error) { 11 | const message = `${error.code ? `(${error.code}) ` : '' }Route: ${req.url} - ${error.message}`; 12 | 13 | if (error.code === 404) { 14 | winston.info(message); 15 | } 16 | else { 17 | winston.error(message); 18 | winston.info(error.stack); 19 | } 20 | } 21 | 22 | next(error); 23 | }; 24 | }; 25 | -------------------------------------------------------------------------------- /src/middleware/not-found-handler.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const errors = require('feathers-errors'); 4 | 5 | module.exports = function() { 6 | return function(req, res, next) { 7 | next(new errors.NotFound('Page not found')); 8 | }; 9 | }; 10 | -------------------------------------------------------------------------------- /src/routes.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const express = require('express'); 3 | 4 | module.exports = function() { 5 | const app = this; 6 | 7 | if(process.env.PRERENDER_TOKEN) { 8 | console.log('Using prerender service') 9 | app.use(require('prerender-node').set('prerenderServiceUrl', 'http://prerender:3000/')) 10 | } 11 | 12 | app.use( express.static(app.get('clientPath')) ); 13 | 14 | 15 | // render home page 16 | app.get('/themes/*', (req, res) => { 17 | try { 18 | var value = path.join(app.get('clientPath'), 'themes', req.params[0]); 19 | res.send( path.join(app.get('clientPath'), 'themes', req.params[0]) ); 20 | } catch(err) { 21 | console.log("err", err); 22 | res.status(500).send(err); 23 | } 24 | }); 25 | 26 | app.get('/extensions/*', (req, res) => { 27 | try { 28 | res.render(path.join(app.get('clientPath'), 'extensions', req.params[0])); 29 | } catch(err) { 30 | res.status(500).send(err); 31 | } 32 | }); 33 | 34 | app.get('/cms/?*', (req, res) => { 35 | res.sendFile(path.join(app.get('adminPath'), 'index.html')); 36 | }); 37 | 38 | app.get('/*', (req, res) => { 39 | res.sendFile(path.join(app.get('appPath'), 'index.html')); 40 | }); 41 | }; 42 | -------------------------------------------------------------------------------- /src/services/authentication/hooks/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const globalHooks = require('../../../hooks'); 4 | const hooks = require('feathers-hooks'); 5 | const auth = require('feathers-authentication').hooks; 6 | 7 | const permissionName = 'manageUsers'; 8 | 9 | exports.before = { 10 | all: [], 11 | find: [ 12 | 13 | ], 14 | get: [ 15 | 16 | ], 17 | create: [ 18 | globalHooks.isTargetEnabled(), 19 | ], 20 | update: [ 21 | 22 | ], 23 | patch: [ 24 | 25 | ], 26 | remove: [ 27 | 28 | ] 29 | }; 30 | 31 | exports.after = { 32 | all: [ 33 | ], 34 | find: [ 35 | ], 36 | get: [ 37 | ], 38 | create: [ 39 | ], 40 | update: [], 41 | patch: [], 42 | remove: [] 43 | }; 44 | -------------------------------------------------------------------------------- /src/services/ban/ban-model.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // ban-model.js - A mongoose model 4 | // 5 | // See http://mongoosejs.com/docs/models.html 6 | // for more of what you can do here. 7 | 8 | const mongoose = require('mongoose'); 9 | const Schema = mongoose.Schema; 10 | const patterns = require('../../components/patterns'); 11 | const validators = require('mongoose-validators'); 12 | 13 | const banSchema = new Schema({ 14 | email: { 15 | type: String, 16 | lowercase: true, 17 | unique: true, 18 | validate: validators.isEmail({skipEmpty:true}) 19 | }, 20 | ip: { 21 | type: String, 22 | trim: true, 23 | validate: validators.isIP({skipEmpty: true}) 24 | } 25 | }); 26 | 27 | const banModel = mongoose.model('ban', banSchema); 28 | 29 | module.exports = banModel; 30 | -------------------------------------------------------------------------------- /src/services/ban/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const service = require('feathers-mongoose'); 4 | const ban = require('./ban-model'); 5 | const hooks = require('./hooks'); 6 | 7 | module.exports = function() { 8 | const app = this; 9 | 10 | const options = { 11 | Model: ban, 12 | lean: true 13 | }; 14 | 15 | // Initialize our service with any options it requires 16 | app.use('/bans', service(options)); 17 | 18 | // Get our initialize service to that we can bind hooks 19 | const banService = app.service('/bans'); 20 | 21 | // Set up our before hooks 22 | banService.before(hooks.before); 23 | 24 | // Set up our after hooks 25 | banService.after(hooks.after); 26 | }; 27 | -------------------------------------------------------------------------------- /src/services/comments/comments-model.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // comments-model.js - A mongoose model 4 | // 5 | // See http://mongoosejs.com/docs/models.html 6 | // for more of what you can do here. 7 | 8 | const mongoose = require('mongoose'); 9 | const Schema = mongoose.Schema; 10 | const patterns = require('../../components/patterns'); 11 | const validators = require('mongoose-validators'); 12 | 13 | const commentsSchema = new Schema({ 14 | author: { 15 | type: String, 16 | trim: true, 17 | default: 'anonymous' 18 | }, 19 | content: { 20 | type: String, 21 | trim: true, 22 | required: true, 23 | validate: validators.matches(patterns.isText) 24 | }, 25 | url: { 26 | type: String, 27 | validate: validators.matches(patterns.isURI, {skipEmpty: true}) 28 | }, 29 | date: { 30 | type: Date, 31 | default: Date.now 32 | }, 33 | email: { 34 | type: String, 35 | lowercase: true, 36 | validate: validators.isEmail({skipEmpty:true}) 37 | }, 38 | ip: { 39 | type: String, 40 | trim: true, 41 | validate: validators.isIP({skipEmpty: true}) 42 | }, 43 | gravatar: { 44 | type: String, 45 | validate: validators.matches(patterns.isURI, {skipEmpty: true}) 46 | }, 47 | approved: { 48 | type: Boolean, 49 | default: false, 50 | required: true 51 | }, 52 | likes: Number, 53 | meta: Object, 54 | createdAt: { type: Date, 'default': Date.now }, 55 | updatedAt: { type: Date, 'default': Date.now } 56 | }); 57 | 58 | const commentsModel = mongoose.model('comments', commentsSchema); 59 | 60 | module.exports = commentsModel; 61 | -------------------------------------------------------------------------------- /src/services/comments/hooks/are-comments-permitted.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | import errors from 'feathers-errors'; 3 | 4 | module.exports = (options) => { 5 | return async (hook, next) => { 6 | 7 | if (!hook.params.provider) { 8 | return next(); 9 | } 10 | 11 | try { 12 | const isDisabled = await hook.app.service('settings').find({query: {name: 'disable-comments'}}) || false; 13 | 14 | if(_.isEmpty(hook.data)) { return next(new errors.BadRequest('The comment did not contain any content.')); } 15 | 16 | if(isDisabled.length === 0 || !isDisabled[0].value) { 17 | // Setup query for banned commentors 18 | var findBanned; 19 | if(hook.data.email) { 20 | findBanned = {$or: [ {ip: hook.params.ip}, {email: hook.data.email} ]}; 21 | } else { 22 | findBanned = {ip: hook.params.ip}; 23 | } 24 | 25 | const isMemberBanned = await hook.app.service('bans').find({query:findBanned}); 26 | 27 | if(Array.isArray(isMemberBanned) && isMemberBanned.length === 0) { 28 | return next() 29 | } else { 30 | return next(new errors.Forbidden('Sorry but you have been banned from commenting on this site.')); 31 | } 32 | 33 | } else { 34 | return next(new errors.Unavailable('Comments are disabled for this site')); 35 | } 36 | } catch(err) { 37 | console.log('error permitting comment', err); 38 | return next(err); 39 | } 40 | 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/services/comments/hooks/is-approved.js: -------------------------------------------------------------------------------- 1 | module.exports = options => { 2 | return async hook => { 3 | let autoAccept; 4 | if(hook.data) { 5 | autoAccept = await hook.app.service('settings').find({query: {name: 'auto-accept-comments'}}); 6 | } else { 7 | return Promise.resolve(hook); 8 | } 9 | 10 | if(autoAccept.length > 0) { 11 | hook.data.approved = autoAccept[0].value; 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/services/comments/hooks/prepend-slash.js: -------------------------------------------------------------------------------- 1 | module.exports = options => { 2 | return (hook) => { 3 | if(hook.data && hook.data.url && hook.data.url.charAt(0) !== '/') { 4 | hook.data.url = '/' + hook.data.url; 5 | } 6 | 7 | if(hook.params.query && hook.params.query.url && hook.params.query.url.charAt(0) !== '/') { 8 | hook.params.query.url = '/' + hook.params.query.url; 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/services/comments/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const service = require('feathers-mongoose'); 4 | const comments = require('./comments-model'); 5 | const hooks = require('./hooks'); 6 | 7 | module.exports = function() { 8 | const app = this; 9 | 10 | const options = { 11 | Model: comments, 12 | lean: true, 13 | paginate: { 14 | default: 30 15 | } 16 | }; 17 | 18 | // Initialize our service with any options it requires 19 | app.use('/comments', service(options)); 20 | 21 | // Get our initialize service to that we can bind hooks 22 | const commentsService = app.service('/comments'); 23 | 24 | // Set up our before hooks 25 | commentsService.before(hooks.before); 26 | 27 | // Set up our after hooks 28 | commentsService.after(hooks.after); 29 | }; 30 | -------------------------------------------------------------------------------- /src/services/custom/custom-model.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // custom-model.js - A mongoose model 4 | // 5 | // See http://mongoosejs.com/docs/models.html 6 | // for more of what you can do here. 7 | 8 | const mongoose = require('mongoose'); 9 | const Schema = mongoose.Schema; 10 | 11 | const customSchema = new Schema({ 12 | belongsTo: { 13 | type: String, 14 | required: true 15 | }, 16 | key: { 17 | type: String, 18 | required: true 19 | }, 20 | enabled: { 21 | type: Boolean, 22 | default: false 23 | }, 24 | value: Schema.Types.Mixed, 25 | permission: { 26 | type: String 27 | }, 28 | createdAt: { type: Date, 'default': Date.now }, 29 | updatedAt: { type: Date, 'default': Date.now } 30 | }); 31 | 32 | customSchema.index({belongsTo: 1, key: 1}, {unique: true}); 33 | 34 | const customModel = mongoose.model('custom', customSchema); 35 | 36 | module.exports = customModel; 37 | -------------------------------------------------------------------------------- /src/services/custom/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const service = require('feathers-mongoose'); 4 | const custom = require('./custom-model'); 5 | const hooks = require('./hooks'); 6 | 7 | module.exports = function() { 8 | const app = this; 9 | 10 | const options = { 11 | Model: custom, 12 | lean: true 13 | }; 14 | 15 | // Initialize our service with any options it requires 16 | app.use('/custom', service(options)); 17 | 18 | // Get our initialize service to that we can bind hooks 19 | const customService = app.service('/custom'); 20 | 21 | // Set up our before hooks 22 | customService.before(hooks.before); 23 | 24 | // Set up our after hooks 25 | customService.after(hooks.after); 26 | }; 27 | -------------------------------------------------------------------------------- /src/services/email/hooks/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const globalHooks = require('../../../hooks'); 4 | const hooks = require('feathers-hooks'); 5 | const auth = require('feathers-authentication').hooks; 6 | 7 | exports.before = { 8 | all: [ 9 | hooks.disable('external') 10 | // auth.verifyToken(), 11 | // auth.populateUser(), 12 | // auth.restrictToAuthenticated() 13 | ], 14 | find: [], 15 | get: [], 16 | create: [], 17 | update: [], 18 | patch: [], 19 | remove: [] 20 | }; 21 | 22 | exports.after = { 23 | all: [], 24 | find: [], 25 | get: [], 26 | create: [], 27 | update: [], 28 | patch: [], 29 | remove: [] 30 | }; 31 | -------------------------------------------------------------------------------- /src/services/email/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const hooks = require('./hooks'); 4 | 5 | import Mailer from 'feathers-mailer' 6 | import smtpTransport from 'nodemailer-smtp-transport' 7 | 8 | module.exports = function(){ 9 | const app = this; 10 | 11 | // Initialize our service with any options it requires 12 | app.use('/emails', Mailer(smtpTransport({ 13 | service: 'gmail', 14 | auth: { 15 | user: process.env.EMAIL_USER, 16 | pass: process.env.EMAIL_PASS 17 | } 18 | }))); 19 | 20 | // Get our initialize service to that we can bind hooks 21 | const emailService = app.service('/emails'); 22 | 23 | // Set up our before hooks 24 | emailService.before(hooks.before); 25 | 26 | // Set up our after hooks 27 | emailService.after(hooks.after); 28 | }; 29 | -------------------------------------------------------------------------------- /src/services/extension-uploads/extension-uploads.service.js: -------------------------------------------------------------------------------- 1 | import feathersErrors from 'feathers-errors' 2 | import _ from 'lodash' 3 | 4 | export default class ImageUploadService { 5 | 6 | setup(app) { 7 | this.app = app; 8 | } 9 | 10 | create(data, params) { 11 | return new Promise((resolve, reject) => { 12 | if(data && !_.isEmpty(data)) { 13 | this.app.service('extensions').create(data).then(function(found) { 14 | return resolve(found); 15 | }).catch(function(err) { 16 | console.log("error uploading theme", err); 17 | return reject(err); 18 | }); 19 | } else { 20 | return reject(new feathersErrors.Unprocessable('The extension did not have a valid extension content.')); 21 | } 22 | 23 | }); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/services/extension-uploads/hooks/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import examineExtension from './examine-extension'; 4 | 5 | const globalHooks = require('../../../hooks'); 6 | const hooks = require('feathers-hooks'); 7 | const auth = require('feathers-authentication').hooks; 8 | 9 | const permissionName = 'changeSiteSettings'; 10 | 11 | exports.before = { 12 | create: [ 13 | auth.verifyToken(), 14 | auth.populateUser(), 15 | auth.restrictToAuthenticated(), 16 | globalHooks.attachPermissions(), 17 | globalHooks.isEnabled(), 18 | globalHooks.hasPermission(permissionName), 19 | examineExtension() 20 | ] 21 | }; 22 | 23 | exports.after = { 24 | all: [], 25 | find: [], 26 | get: [], 27 | create: [ 28 | 29 | ], 30 | update: [], 31 | patch: [], 32 | remove: [] 33 | }; 34 | -------------------------------------------------------------------------------- /src/services/extension-uploads/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import ExtensionUploadsService from './extension-uploads.service'; 4 | const hooks = require('./hooks'); 5 | import unzip from '../../hooks/unzip' 6 | 7 | module.exports = function() { 8 | const app = this; 9 | 10 | app.use('/extension-uploads', 11 | unzip({folderPathProperty: 'extensionsPath', setProperty: 'extensionUrl'}), 12 | new ExtensionUploadsService() 13 | ); 14 | 15 | // Get our initialize service to that we can bind hooks 16 | const extensionUploadsService = app.service('/extension-uploads'); 17 | // 18 | // // Set up our before hooks 19 | extensionUploadsService.before(hooks.before); 20 | // 21 | // // Set up our after hooks 22 | extensionUploadsService.after(hooks.after); 23 | }; 24 | -------------------------------------------------------------------------------- /src/services/extensions/extensions-model.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // extensions-model.js - A mongoose model 4 | // 5 | // See http://mongoosejs.com/docs/models.html 6 | // for more of what you can do here. 7 | 8 | const mongoose = require('mongoose'); 9 | const Schema = mongoose.Schema; 10 | const patterns = require('../../components/patterns'); 11 | const validators = require('mongoose-validators'); 12 | 13 | const extensionsSchema = new Schema({ 14 | label: { 15 | type: String, 16 | required: true, 17 | trim: true 18 | }, 19 | html: { 20 | type: String, 21 | required: true, 22 | validate: validators.matches(patterns.isFilePath) 23 | }, 24 | folder: { 25 | type: String, 26 | required: true, 27 | validate: validators.matches(patterns.isFilePath) 28 | }, 29 | contents: { 30 | type: String, 31 | required: true, 32 | validate: validators.matches(patterns.isFilePath) 33 | }, 34 | screenshot: { 35 | type: String, 36 | required: false, 37 | validate: validators.matches(patterns.isFilePath, {skipEmpty: true}) 38 | }, 39 | active: { 40 | type: Boolean, 41 | default: true 42 | }, 43 | createdAt: { type: Date, 'default': Date.now }, 44 | updatedAt: { type: Date, 'default': Date.now } 45 | }); 46 | 47 | const extensionsModel = mongoose.model('extensions', extensionsSchema); 48 | 49 | module.exports = extensionsModel; 50 | -------------------------------------------------------------------------------- /src/services/extensions/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const service = require('feathers-mongoose'); 4 | const extensions = require('./extensions-model'); 5 | const hooks = require('./hooks'); 6 | 7 | module.exports = function() { 8 | const app = this; 9 | 10 | const options = { 11 | Model: extensions, 12 | lean: true 13 | }; 14 | 15 | // Initialize our service with any options it requires 16 | app.use('/extensions', service(options)); 17 | 18 | // Get our initialize service to that we can bind hooks 19 | const extensionsService = app.service('/extensions'); 20 | 21 | // Set up our before hooks 22 | extensionsService.before(hooks.before); 23 | 24 | // Set up our after hooks 25 | extensionsService.after(hooks.after); 26 | }; 27 | -------------------------------------------------------------------------------- /src/services/image-uploads/hooks/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import resize from './resize'; 4 | 5 | const globalHooks = require('../../../hooks'); 6 | const hooks = require('feathers-hooks'); 7 | const auth = require('feathers-authentication').hooks; 8 | const dauria = require('dauria'); 9 | 10 | const permissionName = 'manageMedia'; 11 | 12 | exports.before = { 13 | create: [ 14 | auth.verifyToken(), 15 | auth.populateUser(), 16 | auth.restrictToAuthenticated(), 17 | globalHooks.attachPermissions(), 18 | globalHooks.isEnabled(), 19 | globalHooks.hasPermission(permissionName), 20 | resize() 21 | ] 22 | }; 23 | 24 | exports.after = { 25 | all: [], 26 | find: [], 27 | get: [], 28 | create: [ 29 | 30 | ], 31 | update: [], 32 | patch: [], 33 | remove: [] 34 | }; 35 | -------------------------------------------------------------------------------- /src/services/image-uploads/image-uploads.service.js: -------------------------------------------------------------------------------- 1 | 2 | export default class ImageUploadService { 3 | 4 | setup(app) { 5 | this.app = app; 6 | } 7 | 8 | create(data, params) { 9 | return new Promise((resolve, reject) => { 10 | const destination = params.file.destination.replace(this.app.get('clientPath'), ''); 11 | 12 | data = { 13 | url: destination, 14 | filename: params.file.filename, 15 | galleries: data.galleries? [data.galleries]: [] 16 | }; 17 | 18 | this.app.service('images').create(data).then(function(found) { 19 | resolve(found); 20 | }).catch(function(err) { 21 | console.log("error uploading image", err); 22 | reject(err); 23 | }); 24 | }); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/services/images/hooks/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const globalHooks = require('../../../hooks'); 4 | const hooks = require('feathers-hooks'); 5 | const auth = require('feathers-authentication').hooks; 6 | 7 | const permissionName = 'manageMedia'; 8 | 9 | exports.before = { 10 | all: [], 11 | find: [ 12 | 13 | ], 14 | get: [ 15 | 16 | ], 17 | create: [ 18 | auth.verifyToken(), 19 | auth.populateUser(), 20 | auth.restrictToAuthenticated(), 21 | globalHooks.attachPermissions(), 22 | globalHooks.isEnabled(), 23 | globalHooks.hasPermission(permissionName) 24 | ], 25 | update: [ 26 | auth.verifyToken(), 27 | auth.populateUser(), 28 | auth.restrictToAuthenticated(), 29 | globalHooks.attachPermissions(), 30 | globalHooks.isEnabled(), 31 | globalHooks.hasPermission(permissionName) 32 | ], 33 | patch: [ 34 | globalHooks.allowUpsert(), 35 | auth.verifyToken(), 36 | auth.populateUser(), 37 | auth.restrictToAuthenticated(), 38 | globalHooks.attachPermissions(), 39 | globalHooks.isEnabled(), 40 | globalHooks.hasPermission(permissionName) 41 | ], 42 | remove: [ 43 | auth.verifyToken(), 44 | auth.populateUser(), 45 | auth.restrictToAuthenticated(), 46 | globalHooks.attachPermissions(), 47 | globalHooks.isEnabled(), 48 | globalHooks.hasPermission(permissionName), 49 | globalHooks.removeFromDisk({ 50 | service: 'images', 51 | containerProperty: 'clientPath', 52 | folderNameProperty: 'url' 53 | }) 54 | ] 55 | }; 56 | 57 | exports.after = { 58 | all: [], 59 | find: [], 60 | get: [], 61 | create: [], 62 | update: [], 63 | patch: [], 64 | remove: [ 65 | 66 | ] 67 | }; 68 | -------------------------------------------------------------------------------- /src/services/images/images-model.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // images-model.js - A mongoose model 4 | // 5 | // See http://mongoosejs.com/docs/models.html 6 | // for more of what you can do here. 7 | 8 | const mongoose = require('mongoose'); 9 | const Schema = mongoose.Schema; 10 | const patterns = require('../../components/patterns'); 11 | const validators = require('mongoose-validators'); 12 | 13 | const imagesSchema = new Schema({ 14 | url: { 15 | type: String, 16 | required: true, 17 | validate: validators.matches(patterns.isFilePath) 18 | }, 19 | filename: { 20 | type: String, 21 | required: true, 22 | validate: validators.matches(patterns.isFilePath) 23 | }, 24 | alt: { 25 | type: String, 26 | trim: true, 27 | default: '', 28 | validate: validators.matches(patterns.isTitle,{skipEmpty:true}) 29 | }, 30 | attribute: { 31 | type: String, 32 | trim: true, 33 | default: '', 34 | validate: validators.matches(patterns.isTitle,{skipEmpty:true}) 35 | }, 36 | groups: [{ 37 | type: String, 38 | trim: true, 39 | validate: validators.matches(patterns.isTitle,{skipEmpty:true}) 40 | }], 41 | galleries: [{ 42 | type: String, 43 | trim: true, 44 | validate: validators.matches(patterns.isTitle,{skipEmpty:true}) 45 | }], 46 | createdAt: { type: Date, 'default': Date.now }, 47 | updatedAt: { type: Date, 'default': Date.now } 48 | }); 49 | 50 | const imagesModel = mongoose.model('images', imagesSchema); 51 | 52 | module.exports = imagesModel; 53 | -------------------------------------------------------------------------------- /src/services/images/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const service = require('feathers-mongoose'); 4 | const images = require('./images-model'); 5 | const hooks = require('./hooks'); 6 | 7 | module.exports = function() { 8 | const app = this; 9 | 10 | const options = { 11 | Model: images, 12 | lean: true 13 | }; 14 | 15 | // Initialize our service with any options it requires 16 | app.use('/images', service(options)); 17 | 18 | // Get our initialize service to that we can bind hooks 19 | const imagesService = app.service('/images'); 20 | 21 | // Set up our before hooks 22 | imagesService.before(hooks.before); 23 | 24 | // Set up our after hooks 25 | imagesService.after(hooks.after); 26 | }; 27 | -------------------------------------------------------------------------------- /src/services/import-export/hooks/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const globalHooks = require('../../../hooks'); 4 | const hooks = require('feathers-hooks'); 5 | const auth = require('feathers-authentication').hooks; 6 | 7 | exports.before = { 8 | all: [ 9 | auth.verifyToken(), 10 | auth.populateUser(), 11 | auth.restrictToAuthenticated(), 12 | globalHooks.attachPermissions(), 13 | globalHooks.isEnabled(), 14 | globalHooks.hasPermission('importExportData') 15 | ], 16 | find: [], 17 | get: [], 18 | create: [ 19 | ], 20 | update: [], 21 | patch: [], 22 | remove: [] 23 | }; 24 | 25 | exports.after = { 26 | all: [], 27 | find: [], 28 | get: [], 29 | create: [], 30 | update: [], 31 | patch: [], 32 | remove: [] 33 | }; 34 | -------------------------------------------------------------------------------- /src/services/menus/hooks/array-to-object.js: -------------------------------------------------------------------------------- 1 | export default options => { 2 | return hook => { 3 | 4 | if(!hook.params.provider && !hook.params.forceCall) { return hook; } 5 | 6 | if(hook.result && Array.isArray(hook.result) && hook.result.length > 0) { 7 | let allMenus = [].concat(hook.result), menus = {}, i = 0; 8 | while(i < allMenus.length) { 9 | if(menus[allMenus[i].group] === undefined) { 10 | menus[allMenus[i].group] = []; 11 | } 12 | menus[allMenus[i].group].push(allMenus[i]); 13 | i++; 14 | } 15 | hook.result = menus; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/services/menus/hooks/object-to-array.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | import { objectOfArraysToArrayOfObjects } from '../../../components/utility'; 3 | 4 | export default options => { 5 | return hook => { 6 | 7 | if (!hook.params.provider) { return hook; } 8 | 9 | hook.data = objectOfArraysToArrayOfObjects(hook.data); 10 | return hook; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/services/menus/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const service = require('feathers-mongoose'); 4 | const menus = require('./menus-model'); 5 | const hooks = require('./hooks'); 6 | 7 | module.exports = function() { 8 | const app = this; 9 | 10 | const options = { 11 | Model: menus, 12 | lean: true 13 | }; 14 | 15 | // Initialize our service with any options it requires 16 | app.use('/menus', service(options)); 17 | 18 | // Get our initialize service to that we can bind hooks 19 | const menusService = app.service('/menus'); 20 | 21 | // Set up our before hooks 22 | menusService.before(hooks.before); 23 | 24 | // Set up our after hooks 25 | menusService.after(hooks.after); 26 | }; 27 | -------------------------------------------------------------------------------- /src/services/pages/hooks/convert-for-incoming.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | import {objectOfArraysToArrayOfObjects, objectToArray} from '../../../components/utility'; 3 | 4 | export default function(options) { 5 | return (hook) => { 6 | if (!hook.params.provider || !hook.data) { return hook; } 7 | 8 | if(hook.data.title) { 9 | hook.params.titleWasChanged = true; 10 | } else { 11 | hook.params.titleWasChanged = false; 12 | } 13 | 14 | if(typeof hook.data.url === 'string' && hook.data.url.charAt(0) !== '/') { 15 | hook.data.url = '/' + hook.data.url; 16 | } 17 | 18 | if(hook.data.extensions) { 19 | hook.data.extensions = objectOfArraysToArrayOfObjects(hook.data.extensions); 20 | } 21 | 22 | if(hook.data.images) { 23 | // hook.data.images = objectToArray(hook.data.images); 24 | hook.data.images = objectToArray(hook.data.images); 25 | } 26 | 27 | return hook; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/services/pages/hooks/notify-subscribers.js: -------------------------------------------------------------------------------- 1 | 2 | import errors from 'feathers-errors' 3 | 4 | export default options => { 5 | return async hook => { 6 | if (!hook.params.provider) { return hook; } 7 | 8 | let id = hook.id 9 | if(!id) { 10 | id = hook.data._id 11 | } 12 | 13 | if(hook.data && hook.data.published && id) { 14 | try { 15 | let response = await hook.app.service('pages').get(id) 16 | 17 | if(response && response.published === false) { 18 | console.log('page was published'); 19 | hook.app.service('subscribe').create({ 20 | "subject": `${response.author} just published ${response.title}.`, 21 | "message": ` 22 |

${response.title}

23 |

${response.description}

24 | Visit ${response.title} 25 |
26 |

Stop following ${hook.params.serverUrl}

27 | ` 28 | }) 29 | } else { 30 | return Promise.resolve(hook) 31 | } 32 | } catch(err) { 33 | console.log('Error checking page previous state', err); 34 | return Promise.resolve(hook) 35 | } 36 | 37 | } 38 | return hook 39 | 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/services/pages/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const service = require('feathers-mongoose'); 4 | const pages = require('./pages-model'); 5 | const hooks = require('./hooks'); 6 | 7 | module.exports = function() { 8 | const app = this; 9 | 10 | const options = { 11 | Model: pages, 12 | lean: true 13 | }; 14 | 15 | // Initialize our service with any options it requires 16 | app.use('/pages', service(options)); 17 | 18 | // Get our initialize service to that we can bind hooks 19 | const pagesService = app.service('/pages'); 20 | 21 | // Set up our before hooks 22 | pagesService.before(hooks.before); 23 | 24 | // Set up our after hooks 25 | pagesService.after(hooks.after); 26 | }; 27 | -------------------------------------------------------------------------------- /src/services/roles/hooks/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const globalHooks = require('../../../hooks'); 4 | const hooks = require('feathers-hooks'); 5 | const auth = require('feathers-authentication').hooks; 6 | 7 | const permissionName = 'manageRoles'; 8 | 9 | exports.before = { 10 | all: [], 11 | find: [ 12 | 13 | ], 14 | get: [ 15 | 16 | ], 17 | create: [ 18 | auth.verifyToken(), 19 | auth.populateUser(), 20 | auth.restrictToAuthenticated(), 21 | globalHooks.attachPermissions(), 22 | globalHooks.isEnabled(), 23 | globalHooks.hasPermission(permissionName) 24 | ], 25 | update: [ 26 | auth.verifyToken(), 27 | auth.populateUser(), 28 | auth.restrictToAuthenticated(), 29 | globalHooks.attachPermissions(), 30 | globalHooks.isEnabled(), 31 | globalHooks.hasPermission(permissionName) 32 | ], 33 | patch: [ 34 | globalHooks.allowUpsert(), 35 | auth.verifyToken(), 36 | auth.populateUser(), 37 | auth.restrictToAuthenticated(), 38 | globalHooks.attachPermissions(), 39 | globalHooks.isEnabled(), 40 | globalHooks.hasPermission(permissionName) 41 | ], 42 | remove: [ 43 | auth.verifyToken(), 44 | auth.populateUser(), 45 | auth.restrictToAuthenticated(), 46 | globalHooks.attachPermissions(), 47 | globalHooks.isEnabled(), 48 | globalHooks.hasPermission(permissionName) 49 | ] 50 | }; 51 | 52 | exports.after = { 53 | all: [], 54 | find: [], 55 | get: [], 56 | create: [], 57 | update: [], 58 | patch: [], 59 | remove: [] 60 | }; 61 | -------------------------------------------------------------------------------- /src/services/roles/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const service = require('feathers-mongoose'); 4 | const roles = require('./roles-model'); 5 | const hooks = require('./hooks'); 6 | 7 | module.exports = function() { 8 | const app = this; 9 | 10 | const options = { 11 | Model: roles, 12 | lean: true 13 | }; 14 | 15 | // Initialize our service with any options it requires 16 | app.use('/roles', service(options)); 17 | 18 | // Get our initialize service to that we can bind hooks 19 | const rolesService = app.service('/roles'); 20 | 21 | // Set up our before hooks 22 | rolesService.before(hooks.before); 23 | 24 | // Set up our after hooks 25 | rolesService.after(hooks.after); 26 | }; 27 | -------------------------------------------------------------------------------- /src/services/settings/hooks/recompile-index.js: -------------------------------------------------------------------------------- 1 | 2 | import compileIndex from '../../../components/compile-index'; 3 | var contains = ['clientID', 'appID', 'verificationID', 'recaptchaClientKey', 'recaptchaKey']; 4 | 5 | module.exports = options => { 6 | const contains = options || ['clientID', 'appID', 'verificationID', 'recaptchaClientKey', 'recaptchaKey']; 7 | return hook => { 8 | if(hook.params.query && contains.indexOf(hook.params.query.name) > -1) { 9 | console.log('recompiling html views'); 10 | compileIndex.call(hook.app); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/services/settings/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const service = require('feathers-mongoose'); 4 | const settings = require('./settings-model'); 5 | const hooks = require('./hooks'); 6 | 7 | module.exports = function() { 8 | const app = this; 9 | 10 | const options = { 11 | Model: settings, 12 | lean: true 13 | }; 14 | 15 | // Initialize our service with any options it requires 16 | app.use('/settings', service(options)); 17 | 18 | // Get our initialize service to that we can bind hooks 19 | const settingsService = app.service('/settings'); 20 | 21 | // Set up our before hooks 22 | settingsService.before(hooks.before); 23 | 24 | // Set up our after hooks 25 | settingsService.after(hooks.after); 26 | }; 27 | -------------------------------------------------------------------------------- /src/services/settings/settings-model.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // settings-model.js - A mongoose model 4 | // 5 | // See http://mongoosejs.com/docs/models.html 6 | // for more of what you can do here. 7 | 8 | const mongoose = require('mongoose'); 9 | const Schema = mongoose.Schema; 10 | const patterns = require('../../components/patterns'); 11 | const validators = require('mongoose-validators'); 12 | 13 | const settingsSchema = new Schema({ 14 | name: { 15 | type: String, 16 | trim: true, 17 | required: true, 18 | unique: true, 19 | validate: validators.matches(patterns.isTitle) 20 | }, 21 | value: { 22 | type: Schema.Types.Mixed, 23 | required: true 24 | } 25 | }); 26 | 27 | const settingsModel = mongoose.model('settings', settingsSchema); 28 | 29 | module.exports = settingsModel; 30 | -------------------------------------------------------------------------------- /src/services/shared-content/hooks/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const globalHooks = require('../../../hooks'); 4 | const hooks = require('feathers-hooks'); 5 | const auth = require('feathers-authentication').hooks; 6 | 7 | const permissionName = 'editContent'; 8 | 9 | exports.before = { 10 | all: [], 11 | find: [ 12 | 13 | ], 14 | get: [ 15 | 16 | ], 17 | create: [ 18 | auth.verifyToken(), 19 | auth.populateUser(), 20 | auth.restrictToAuthenticated(), 21 | globalHooks.attachPermissions(), 22 | globalHooks.isEnabled(), 23 | globalHooks.hasPermission(permissionName) 24 | ], 25 | update: [ 26 | auth.verifyToken(), 27 | auth.populateUser(), 28 | auth.restrictToAuthenticated(), 29 | globalHooks.attachPermissions(), 30 | globalHooks.isEnabled(), 31 | globalHooks.hasPermission(permissionName) 32 | ], 33 | patch: [ 34 | auth.verifyToken(), 35 | auth.populateUser(), 36 | auth.restrictToAuthenticated(), 37 | globalHooks.attachPermissions(), 38 | globalHooks.isEnabled(), 39 | globalHooks.hasPermission(permissionName) 40 | ], 41 | remove: [ 42 | auth.verifyToken(), 43 | auth.populateUser(), 44 | auth.restrictToAuthenticated(), 45 | globalHooks.attachPermissions(), 46 | globalHooks.isEnabled(), 47 | globalHooks.hasPermission(permissionName) 48 | ] 49 | }; 50 | 51 | exports.after = { 52 | all: [], 53 | find: [], 54 | get: [], 55 | create: [], 56 | update: [], 57 | patch: [], 58 | remove: [] 59 | }; 60 | -------------------------------------------------------------------------------- /src/services/shared-content/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const service = require('feathers-mongoose'); 4 | const sharedContent = require('./shared-content-model'); 5 | const hooks = require('./hooks'); 6 | 7 | module.exports = function() { 8 | const app = this; 9 | 10 | const options = { 11 | Model: sharedContent, 12 | lean: true 13 | }; 14 | 15 | // Initialize our service with any options it requires 16 | app.use('/shared-content', service(options)); 17 | 18 | // Get our initialize service to that we can bind hooks 19 | const sharedContentService = app.service('/shared-content'); 20 | 21 | // Set up our before hooks 22 | sharedContentService.before(hooks.before); 23 | 24 | // Set up our after hooks 25 | sharedContentService.after(hooks.after); 26 | }; 27 | -------------------------------------------------------------------------------- /src/services/shared-content/shared-content-model.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // shared-content-model.js - A mongoose model 4 | // 5 | // See http://mongoosejs.com/docs/models.html 6 | // for more of what you can do here. 7 | 8 | const mongoose = require('mongoose'); 9 | const Schema = mongoose.Schema; 10 | const patterns = require('../../components/patterns'); 11 | const validators = require('mongoose-validators'); 12 | 13 | const sharedContentSchema = new Schema({ 14 | contentName: { 15 | type: String, 16 | unique: true, 17 | required: true, 18 | validate: validators.matches(patterns.isTitle) 19 | }, 20 | data: Schema.Types.Mixed, 21 | config: Schema.Types.Mixed, 22 | type: { 23 | type: String, 24 | required: true, 25 | validate: validators.matches(patterns.isTitle) 26 | } 27 | }); 28 | 29 | const sharedContentModel = mongoose.model('shared-content', sharedContentSchema); 30 | 31 | module.exports = sharedContentModel; 32 | -------------------------------------------------------------------------------- /src/services/staging/hooks/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const globalHooks = require('../../../hooks'); 4 | const hooks = require('feathers-hooks'); 5 | const auth = require('feathers-authentication').hooks; 6 | 7 | exports.before = { 8 | all: [ 9 | auth.verifyToken(), 10 | auth.populateUser(), 11 | auth.restrictToAuthenticated(), 12 | globalHooks.attachPermissions(), 13 | globalHooks.isEnabled(), 14 | globalHooks.hasPermission('editContent'), 15 | ], 16 | find: [], 17 | get: [], 18 | create: [], 19 | update: [], 20 | patch: [ 21 | globalHooks.allowUpsert() 22 | ], 23 | remove: [] 24 | }; 25 | 26 | exports.after = { 27 | all: [], 28 | find: [], 29 | get: [], 30 | create: [], 31 | update: [], 32 | patch: [], 33 | remove: [] 34 | }; 35 | -------------------------------------------------------------------------------- /src/services/staging/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const service = require('feathers-mongoose'); 4 | const staging = require('./staging-model'); 5 | const hooks = require('./hooks'); 6 | 7 | module.exports = function() { 8 | const app = this; 9 | 10 | const options = { 11 | Model: staging, 12 | lean: true 13 | }; 14 | 15 | // Initialize our service with any options it requires 16 | app.use('/staging', service(options)); 17 | 18 | // Get our initialize service to that we can bind hooks 19 | const stagingService = app.service('/staging'); 20 | 21 | // Set up our before hooks 22 | stagingService.before(hooks.before); 23 | 24 | // Set up our after hooks 25 | stagingService.after(hooks.after); 26 | }; 27 | -------------------------------------------------------------------------------- /src/services/staging/staging-model.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // staging-model.js - A mongoose model 4 | // 5 | // See http://mongoosejs.com/docs/models.html 6 | // for more of what you can do here. 7 | 8 | const mongoose = require('mongoose'); 9 | const Schema = mongoose.Schema; 10 | 11 | const stagingSchema = new Schema({ 12 | belongsTo: { type: String, required: false, trim: true, default: '' }, 13 | key: { type: String, required: true}, 14 | data: Schema.Types.Mixed, 15 | createdAt: { type: Date, 'default': Date.now }, 16 | updatedAt: { type: Date, 'default': Date.now } 17 | }); 18 | 19 | stagingSchema.index({belongsTo: 1, key: 1}, {unique: true}); 20 | 21 | const stagingModel = mongoose.model('staging', stagingSchema); 22 | 23 | module.exports = stagingModel; 24 | -------------------------------------------------------------------------------- /src/services/subscribe/hooks/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const globalHooks = require('../../../hooks'); 4 | const hooks = require('feathers-hooks'); 5 | 6 | 7 | exports.before = { 8 | all: [], 9 | find: [], 10 | get: [], 11 | create: [ 12 | hooks.disable('external') 13 | ], 14 | update: [], 15 | patch: [], 16 | remove: [] 17 | }; 18 | 19 | exports.after = { 20 | all: [], 21 | find: [], 22 | get: [], 23 | create: [], 24 | update: [], 25 | patch: [], 26 | remove: [] 27 | }; 28 | -------------------------------------------------------------------------------- /src/services/theme-uploads/hooks/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import examineTheme from './examine-theme'; 4 | 5 | const globalHooks = require('../../../hooks'); 6 | const hooks = require('feathers-hooks'); 7 | const auth = require('feathers-authentication').hooks; 8 | 9 | const permissionName = 'changeSiteSettings'; 10 | 11 | exports.before = { 12 | create: [ 13 | auth.verifyToken(), 14 | auth.populateUser(), 15 | auth.restrictToAuthenticated(), 16 | globalHooks.attachPermissions(), 17 | globalHooks.isEnabled(), 18 | globalHooks.hasPermission(permissionName), 19 | examineTheme() 20 | ] 21 | }; 22 | 23 | exports.after = { 24 | all: [], 25 | find: [], 26 | get: [], 27 | create: [ 28 | 29 | ], 30 | update: [], 31 | patch: [], 32 | remove: [] 33 | }; 34 | -------------------------------------------------------------------------------- /src/services/theme-uploads/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import ThemeUploadsService from './theme-uploads.service'; 4 | const hooks = require('./hooks'); 5 | import unzip from '../../hooks/unzip' 6 | 7 | module.exports = function() { 8 | const app = this; 9 | 10 | app.use('/theme-uploads', 11 | unzip({folderPathProperty: 'themesPath', setProperty: 'themeUrl'}), 12 | new ThemeUploadsService() 13 | ); 14 | 15 | // Get our initialize service to that we can bind hooks 16 | const themeUploadsService = app.service('/theme-uploads'); 17 | // 18 | // // Set up our before hooks 19 | themeUploadsService.before(hooks.before); 20 | // 21 | // // Set up our after hooks 22 | themeUploadsService.after(hooks.after); 23 | }; 24 | -------------------------------------------------------------------------------- /src/services/theme-uploads/theme-uploads.service.js: -------------------------------------------------------------------------------- 1 | import feathersErrors from 'feathers-errors' 2 | import _ from 'lodash' 3 | 4 | export default class ImageUploadService { 5 | 6 | setup(app) { 7 | this.app = app; 8 | } 9 | 10 | create(data, params) { 11 | return new Promise((resolve, reject) => { 12 | if(data && !_.isEmpty(data)) { 13 | this.app.service('themes').create(data).then(function(found) { 14 | return resolve(found); 15 | }).catch(function(err) { 16 | console.log("error uploading theme", err); 17 | return reject(err); 18 | }); 19 | } else { 20 | return reject(new feathersErrors.Unprocessable('The theme did not have a valid theme content.')); 21 | } 22 | }); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/services/themes/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const service = require('feathers-mongoose'); 4 | const themes = require('./themes-model'); 5 | const hooks = require('./hooks'); 6 | 7 | module.exports = function() { 8 | const app = this; 9 | 10 | const options = { 11 | Model: themes, 12 | lean: true 13 | }; 14 | 15 | // Initialize our service with any options it requires 16 | app.use('/themes', service(options)); 17 | 18 | // Get our initialize service to that we can bind hooks 19 | const themesService = app.service('/themes'); 20 | 21 | // Set up our before hooks 22 | themesService.before(hooks.before); 23 | 24 | // Set up our after hooks 25 | themesService.after(hooks.after); 26 | }; 27 | -------------------------------------------------------------------------------- /src/services/user/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const service = require('feathers-mongoose'); 4 | const user = require('./user-model'); 5 | const hooks = require('./hooks'); 6 | 7 | module.exports = function() { 8 | const app = this; 9 | 10 | const options = { 11 | Model: user, 12 | lean: true 13 | }; 14 | 15 | // Initialize our service with any options it requires 16 | app.use('/users', service(options)); 17 | 18 | // Get our initialize service to that we can bind hooks 19 | const userService = app.service('/users'); 20 | 21 | // Set up our before hooks 22 | userService.before(hooks.before); 23 | 24 | // Set up our after hooks 25 | userService.after(hooks.after); 26 | }; 27 | -------------------------------------------------------------------------------- /src/services/wordpress-import/hooks/convert-xml-to-json.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const Finder = require('fs-finder'); 3 | const path = require('path'); 4 | const _ = require('lodash'); 5 | import feathersErrors from 'feathers-errors' 6 | import fse from 'fs-extra' 7 | const xml2json = require('xml-to-json'); 8 | 9 | /** 10 | * Requires hook.params.themeUrl to be set. If theme has all necessary content, it sets hook.data to the theme data 11 | */ 12 | export default function(options) { 13 | return hook => { 14 | 15 | return new Promise((resolve, reject) => { 16 | 17 | if(!hook.params.tempFilePath) { return reject('tempFilePath not found.'); } 18 | 19 | return xml2json({input: hook.params.tempFilePath}, function(err, result) { 20 | if(err) { 21 | console.error('Error convert wordpress xml to json', err); 22 | return reject(new feathersErrors.Unprocessable('Could not convert wordpress xml to json')); 23 | } else { 24 | hook.params = Object.assign(hook.params, {jsonData: result}); 25 | return resolve(hook); 26 | } 27 | }); 28 | 29 | }); 30 | } 31 | }; 32 | -------------------------------------------------------------------------------- /src/services/wordpress-import/hooks/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import importWordpress from './import-wordpress'; 4 | import convertXmlToJson from './convert-xml-to-json' 5 | 6 | const globalHooks = require('../../../hooks'); 7 | const hooks = require('feathers-hooks'); 8 | const auth = require('feathers-authentication').hooks; 9 | 10 | const permissionName = 'manageContent'; 11 | 12 | exports.before = { 13 | create: [ 14 | auth.verifyToken(), 15 | auth.populateUser(), 16 | auth.restrictToAuthenticated(), 17 | globalHooks.attachPermissions(), 18 | globalHooks.isEnabled(), 19 | globalHooks.hasPermission(permissionName), 20 | convertXmlToJson(), 21 | importWordpress() 22 | ] 23 | }; 24 | 25 | exports.after = { 26 | all: [], 27 | find: [], 28 | get: [], 29 | create: [], 30 | update: [], 31 | patch: [], 32 | remove: [] 33 | }; 34 | -------------------------------------------------------------------------------- /src/services/wordpress-import/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import WordpressImportService from './wordpress-import.service'; 4 | const hooks = require('./hooks'); 5 | import unzip from './unzip'; 6 | 7 | module.exports = function() { 8 | const app = this; 9 | 10 | app.use('/wordpress-import', 11 | unzip, 12 | new WordpressImportService() 13 | ); 14 | 15 | // Get our initialize service to that we can bind hooks 16 | const wordpressImportService = app.service('/wordpress-import'); 17 | // 18 | // // Set up our before hooks 19 | wordpressImportService.before(hooks.before); 20 | // 21 | // // Set up our after hooks 22 | wordpressImportService.after(hooks.after); 23 | }; 24 | -------------------------------------------------------------------------------- /src/services/wordpress-import/unzip.js: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | const Decompress = require('decompress'); 3 | const zip = require('decompress-unzip'); 4 | const formidable = require('formidable'); 5 | import feathersErrors from 'feathers-errors' 6 | import fs from 'fs'; 7 | 8 | export default function(req, res, next) { 9 | const app = this; 10 | 11 | if(!req.app || !req.app.get('themesPath')) { 12 | next(new Error('themesPath not found on server')); 13 | } 14 | 15 | var createdFolderName = '125098dsflkj1324'; 16 | 17 | var createdFolderPath = path.join(req.app.get('themesPath'), createdFolderName); 18 | try { 19 | var form = new formidable.IncomingForm(); 20 | form.keepExtensions = true; 21 | form.parse(req, function(err, fields, files) { 22 | if(err) { 23 | console.log("Error parsing zip: ", err); 24 | return next(new feathersErrors.NotAcceptable('The zip folder must be compressed in the correct format.')); 25 | } 26 | if(!files || !files.file) { 27 | return next(new feathersErrors.NotAcceptable('The zip folder must be compressed.')); 28 | } 29 | 30 | var tempFilePath = files.file.path; 31 | var fileName = files.file.name; 32 | var contentType = files.file.type; 33 | 34 | req.feathers.tempFilePath = tempFilePath; 35 | next(); 36 | }); 37 | } catch(err) { 38 | return next(err); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/services/wordpress-import/wordpress-import.service.js: -------------------------------------------------------------------------------- 1 | import feathersErrors from 'feathers-errors' 2 | import _ from 'lodash' 3 | 4 | export default class ImageUploadService { 5 | 6 | setup(app) { 7 | this.app = app; 8 | } 9 | 10 | create(data, params) { 11 | return new Promise((resolve, reject) => { 12 | return resolve([]); 13 | // if(data && !_.isEmpty(data)) { 14 | // // this.app.service('themes').create(data).then(function(found) { 15 | // // return resolve(found); 16 | // // }).catch(function(err) { 17 | // // console.log("error uploading theme", err); 18 | // // return reject(err); 19 | // // }); 20 | // return resolve([]); 21 | // } else { 22 | // return reject(new feathersErrors.Unprocessable('The import did not have a valid import content.')); 23 | // } 24 | 25 | }); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /test/app.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | const request = require('request'); 5 | const app = require('../src/app'); 6 | 7 | describe('Feathers application tests', function() { 8 | before(function(done) { 9 | this.server = app.listen(3030); 10 | this.server.once('listening', () => done()); 11 | }); 12 | 13 | after(function(done) { 14 | this.server.close(done); 15 | }); 16 | 17 | it('starts and shows the index page', function(done) { 18 | request('http://localhost:3030', function(err, res, body) { 19 | assert.ok(body.indexOf('') !== -1); 20 | done(err); 21 | }); 22 | }); 23 | 24 | describe('404', function() { 25 | it('shows a 404 HTML page', function(done) { 26 | request({ 27 | url: 'http://localhost:3030/path/to/nowhere', 28 | headers: { 29 | 'Accept': 'text/html' 30 | } 31 | }, function(err, res, body) { 32 | assert.equal(res.statusCode, 404); 33 | assert.ok(body.indexOf('') !== -1); 34 | done(err); 35 | }); 36 | }); 37 | 38 | it('shows a 404 JSON error without stack trace', function(done) { 39 | request({ 40 | url: 'http://localhost:3030/path/to/nowhere', 41 | json: true 42 | }, function(err, res, body) { 43 | assert.equal(res.statusCode, 404); 44 | assert.equal(body.code, 404); 45 | assert.equal(body.message, 'Page not found'); 46 | assert.equal(body.name, 'NotFound'); 47 | done(err); 48 | }); 49 | }); 50 | }); 51 | }); 52 | -------------------------------------------------------------------------------- /test/services/ban/index.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | const app = require('../../../src/app'); 5 | 6 | describe('ban service', function() { 7 | it('registered the bans service', () => { 8 | assert.ok(app.service('bans')); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /test/services/comments/index.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | const app = require('../../../src/app'); 5 | 6 | describe('comments service', function() { 7 | it('registered the comments service', () => { 8 | assert.ok(app.service('comments')); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /test/services/custom/index.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | const app = require('../../../src/app'); 5 | 6 | describe('custom service', function() { 7 | it('registered the customs service', () => { 8 | assert.ok(app.service('customs')); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /test/services/email/index.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | const app = require('../../../src/app'); 5 | 6 | describe('email service', function() { 7 | it('registered the emails service', () => { 8 | assert.ok(app.service('emails')); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /test/services/extension/index.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | const app = require('../../../src/app'); 5 | 6 | describe('extension service', function() { 7 | it('registered the extensions service', () => { 8 | assert.ok(app.service('extensions')); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /test/services/extensions/index.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | const app = require('../../../src/app'); 5 | 6 | describe('extensions service', function() { 7 | it('registered the extensions service', () => { 8 | assert.ok(app.service('extensions')); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /test/services/images/index.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | const app = require('../../../src/app'); 5 | 6 | describe('images service', function() { 7 | it('registered the images service', () => { 8 | assert.ok(app.service('images')); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /test/services/import-export/index.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | const app = require('../../../src/app'); 5 | 6 | describe('import-export service', function() { 7 | it('registered the import-exports service', () => { 8 | assert.ok(app.service('import-exports')); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /test/services/menus/index.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | const app = require('../../../src/app'); 5 | 6 | describe('menus service', function() { 7 | it('registered the menus service', () => { 8 | assert.ok(app.service('menus')); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /test/services/pages/index.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | const app = require('../../../src/app'); 5 | 6 | describe('pages service', function() { 7 | it('registered the pages service', () => { 8 | assert.ok(app.service('pages')); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /test/services/roles/index.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | const app = require('../../../src/app'); 5 | 6 | describe('roles service', function() { 7 | it('registered the roles service', () => { 8 | assert.ok(app.service('roles')); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /test/services/settings/index.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | const app = require('../../../src/app'); 5 | 6 | describe('settings service', function() { 7 | it('registered the settings service', () => { 8 | assert.ok(app.service('settings')); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /test/services/shared-content/index.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | const app = require('../../../src/app'); 5 | 6 | describe('shared-content service', function() { 7 | it('registered the shared-contents service', () => { 8 | assert.ok(app.service('shared-contents')); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /test/services/subscribe/index.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | const app = require('../../../src/app'); 5 | 6 | describe('subscribe service', function() { 7 | it('registered the subscribes service', () => { 8 | assert.ok(app.service('subscribes')); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /test/services/themes/index.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | const app = require('../../../src/app'); 5 | 6 | describe('themes service', function() { 7 | it('registered the themes service', () => { 8 | assert.ok(app.service('themes')); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /test/services/user/index.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | const app = require('../../../src/app'); 5 | 6 | describe('user service', function() { 7 | it('registered the users service', () => { 8 | assert.ok(app.service('users')); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /update.sh: -------------------------------------------------------------------------------- 1 | # Stop server 2 | sudo nginx -s quit; pm2 stop all; sudo mongo 127.0.0.1/admin --eval "db.shutdownServer()"; 3 | 4 | # Grab Updates 5 | git pull 6 | npm install 7 | sudo bower install --allow-root --config.interactive=false 8 | gulp build 9 | gulp injectBuild 10 | gulp build-themes 11 | 12 | export NODE_ENV=production 13 | # Start server 14 | sudo mongod --smallfiles --fork --logpath /var/log/mongodb.log; pm2 start dist/server/app.js; sudo nginx 15 | -------------------------------------------------------------------------------- /views/admin.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | --------------------------------------------------------------------------------