├── blog ├── .gitattributes ├── public ├── admin │ ├── code │ │ ├── import │ │ │ ├── import.styl │ │ │ ├── import.js │ │ │ └── import.controller.spec.js │ │ ├── analytics │ │ │ ├── analytics.styl │ │ │ ├── analytics.js │ │ │ ├── analytics.controller.spec.js │ │ │ └── google-analytics-embed-customizations.js │ │ ├── components │ │ │ ├── camel-to-human │ │ │ │ ├── camel-to-human.filter.js │ │ │ │ └── camel-to-human.filter.spec.js │ │ │ ├── date-picker │ │ │ │ └── date-picker.directive.js │ │ │ ├── mdl │ │ │ │ └── mdl.directive.js │ │ │ └── dialog │ │ │ │ └── dialog.directive.js │ │ ├── media │ │ │ ├── media.styl │ │ │ ├── media.js │ │ │ ├── media.controller.spec.js │ │ │ ├── media.jade │ │ │ └── media.controller.js │ │ ├── pages │ │ │ ├── pages.js │ │ │ ├── pages.styl │ │ │ └── pages.controller.spec.js │ │ ├── themes │ │ │ ├── themes.js │ │ │ ├── themes.controller.spec.js │ │ │ └── themes.styl │ │ ├── comments │ │ │ ├── comments.js │ │ │ ├── comments.controller.spec.js │ │ │ └── comments.styl │ │ ├── extensions │ │ │ ├── extensions.js │ │ │ ├── extensions.controller.spec.js │ │ │ └── extensions.styl │ │ ├── users │ │ │ ├── users.js │ │ │ └── users.controller.spec.js │ │ ├── cms │ │ │ ├── cms.js │ │ │ ├── cms.controller.spec.js │ │ │ └── cms.jade │ │ └── account │ │ │ ├── settings │ │ │ ├── settings.controller.js │ │ │ └── settings.jade │ │ │ ├── account.js │ │ │ └── login │ │ │ └── login.styl │ └── fonts │ │ ├── FontAwesome.otf │ │ ├── fontawesome-webfont.eot │ │ ├── fontawesome-webfont.ttf │ │ ├── fontawesome-webfont.woff │ │ └── fontawesome-webfont.woff2 ├── app │ ├── components │ │ ├── mb-link │ │ │ ├── mb-link.styl │ │ │ └── mb-link.directive.js │ │ ├── mb-choose-link │ │ │ ├── mb-choose-link.styl │ │ │ ├── mb-edit-link.service.js │ │ │ ├── mb-edit-link.controller.js │ │ │ └── mb-choose-link.directive.js │ │ ├── mb-list-area │ │ │ ├── mb-list-area.directive.spec.js │ │ │ ├── mb-list-area.styl │ │ │ ├── mb-list-area.jade │ │ │ └── mb-list-area-directive.js │ │ ├── mb-choose-icon │ │ │ ├── mb-choose-icon.styl │ │ │ └── mb-edit-icon.service.js │ │ ├── meanbase-editable │ │ │ ├── icons.png │ │ │ ├── icons-2x.png │ │ │ └── meanbase-editable.directive.spec.js │ │ ├── mb-choose-image │ │ │ └── mb-choose-image.styl │ │ ├── mb-find-images-modal │ │ │ ├── mb-find-images-modal.styl │ │ │ ├── mb-find-image.modal.jade │ │ │ ├── mb-find-images-modal.directive.spec.js │ │ │ ├── mb-find-images-modal.directive.js │ │ │ └── mb-find-image.service.js │ │ ├── mb-list-selector │ │ │ ├── mb-list-selector.jade │ │ │ ├── mb-list-selector.styl │ │ │ └── mb-list.modal.jade │ │ ├── mb-edit-link │ │ │ ├── mb-edit-link.styl │ │ │ └── mb-edit-link.directive.js │ │ ├── main │ │ │ ├── main.jade │ │ │ └── main.controller.spec.js │ │ ├── mb-list-remove │ │ │ ├── mb-list-remove.styl │ │ │ └── mb-list-remove.directive.js │ │ ├── mb-extension-edit │ │ │ ├── mb-extension-edit.styl │ │ │ ├── mb-extension-edit.directive.js │ │ │ └── mb-extension-edit.modal.jade │ │ ├── mb-edit-menu │ │ │ ├── mb-edit-menu.styl │ │ │ ├── mb-edit-menu.directive.js │ │ │ └── mb-edit-menu.service.js │ │ ├── mb-dynamic-html │ │ │ ├── mb-dynamic-html.directive.js │ │ │ └── mb-dynamic-html.directive.spec.js │ │ ├── cms.headbar │ │ │ ├── choose-link.modal.jade │ │ │ └── editmodal.modal.jade │ │ ├── mb-init-list │ │ │ └── init-list.directive.js │ │ ├── mb-recaptcha │ │ │ └── mb-recaptcha.directive.js │ │ ├── mb-icon │ │ │ └── mb-icon.directive.js │ │ ├── mb-grid-item │ │ │ └── mb-grid-item.styl │ │ ├── mb-add-menu-item │ │ │ ├── mb-add-menu-item.service.js │ │ │ ├── mb-add-menu-item.directive.js │ │ │ └── mb-add-menu-item.controller.js │ │ ├── mb-list-add │ │ │ ├── mb-list-add.styl │ │ │ └── mb-list-add.directive.js │ │ └── mb-text │ │ │ └── mb-text.styl │ ├── robots.txt │ ├── fonts │ │ ├── FontAwesome.otf │ │ ├── fontawesome-webfont.eot │ │ ├── fontawesome-webfont.ttf │ │ ├── fontawesome-webfont.woff │ │ └── fontawesome-webfont.woff2 │ └── app.styl ├── extensions │ ├── mb-search │ │ ├── search-form.styl │ │ ├── mb-search-box-extension.html │ │ ├── screenshot.png │ │ ├── index.js │ │ ├── search-form.html │ │ └── search-form.directive.js │ ├── md-selling-point-list │ │ ├── selling-point-extension.html │ │ ├── selling-point-list.styl │ │ ├── index.js │ │ ├── toggle-type.modal.html │ │ ├── selling-point-list.directive.js │ │ └── selling-point-list.html │ ├── mb-subscribe │ │ ├── index.js │ │ └── subscribe-form-extension.jade │ ├── md-bullet-point-list │ │ ├── index.js │ │ └── bullet-point-list-extension.html │ └── .gitignore ├── themes │ ├── meanbase-demo │ │ ├── templates │ │ │ ├── blog │ │ │ │ ├── blog.styl │ │ │ │ └── blog-screenshot.png │ │ │ ├── archive │ │ │ │ ├── archive.styl │ │ │ │ ├── archive-screenshot.png │ │ │ │ └── archive.controller.js │ │ │ └── home │ │ │ │ └── home-screenshot.png │ │ ├── img │ │ │ ├── dog.png │ │ │ ├── ipad.png │ │ │ ├── phones.png │ │ │ ├── banner-bg.jpg │ │ │ └── intro-bg.jpg │ │ ├── screenshot.png │ │ ├── theme.json │ │ ├── index.js │ │ ├── LICENSE │ │ └── README.md │ └── .gitignore ├── shared │ ├── sortable │ │ ├── sortable.jade │ │ ├── sortable.directive.spec.js │ │ ├── sortable.styl │ │ └── sortable.directive.js │ ├── missing │ │ ├── missing.controller.js │ │ ├── missing.js │ │ ├── missing.jade │ │ ├── missing.controller.spec.js │ │ └── missing.styl │ ├── taglist │ │ ├── taglist.jade │ │ ├── taglist.directive.spec.js │ │ ├── taglist.styl │ │ └── taglist.directive.js │ ├── validate │ │ ├── validate.jade │ │ ├── validate.styl │ │ └── validate.directive.spec.js │ ├── htmlToPlainText │ │ └── htmlToPlainText.filter.js │ ├── api │ │ ├── api.service.spec.js │ │ └── api.service.js │ ├── helpers │ │ └── helpers.service.spec.js │ ├── endpoints │ │ └── endpoints.service.spec.js │ ├── fallback-src │ │ ├── fallback-src.directive.js │ │ └── fallback-src.directive.spec.js │ ├── ng-enter │ │ ├── ng-enter.directive.js │ │ └── ng-enter.directive.spec.js │ ├── mongoose-error │ │ └── mongoose-error.directive.js │ ├── doubleClick │ │ ├── doubleClick.directive.spec.js │ │ └── doubleClick.directive.js │ ├── mb-animate │ │ ├── mb-animate.directive.spec.js │ │ └── mb-animate.directive.js │ ├── image-selector │ │ └── image-selector.directive.spec.js │ └── feathers │ │ └── feathers.service.js └── assets │ └── .gitignore ├── meanbase-logo.png ├── Meanbase-Sidebar.png ├── data └── .gitignore ├── Meanbase-Frontend.png ├── export ├── export │ └── .gitignore ├── import │ └── .gitignore └── .gitignore ├── Meanbase-Themes-Page.png ├── meanbase-screenshot.png ├── .babelrc ├── .travis.yml ├── .dockerignore ├── src ├── hooks │ ├── recaptcha.js │ ├── owner-or-restrict-changes.js │ ├── allow-upsert.js │ ├── is-enabled.js │ ├── if-password-then-hash.js │ ├── has-permission.js │ ├── delete-custom-data.js │ ├── permission-or-restrict-changes.js │ ├── is-target-enabled.js │ ├── attach-permissions.js │ ├── send-verification-email.js │ ├── has-permission-or-restrict.js │ └── filter-by-permission-or-restrict.js ├── middleware │ ├── not-found-handler.js │ ├── index.js │ └── logger.js ├── index.js ├── services │ ├── menus │ │ ├── hooks │ │ │ ├── object-to-array.js │ │ │ └── array-to-object.js │ │ └── index.js │ ├── comments │ │ ├── hooks │ │ │ ├── prepend-slash.js │ │ │ ├── is-approved.js │ │ │ └── are-comments-permitted.js │ │ ├── index.js │ │ └── comments-model.js │ ├── subscribe │ │ └── hooks │ │ │ └── index.js │ ├── settings │ │ ├── hooks │ │ │ └── recompile-index.js │ │ ├── index.js │ │ └── settings-model.js │ ├── email │ │ ├── hooks │ │ │ └── index.js │ │ └── index.js │ ├── ban │ │ ├── index.js │ │ └── ban-model.js │ ├── wordpress-import │ │ ├── index.js │ │ ├── hooks │ │ │ ├── index.js │ │ │ └── convert-xml-to-json.js │ │ ├── wordpress-import.service.js │ │ └── unzip.js │ ├── user │ │ └── index.js │ ├── pages │ │ ├── index.js │ │ └── hooks │ │ │ ├── convert-for-incoming.js │ │ │ └── notify-subscribers.js │ ├── roles │ │ ├── index.js │ │ └── hooks │ │ │ └── index.js │ ├── authentication │ │ └── hooks │ │ │ └── index.js │ ├── custom │ │ ├── index.js │ │ └── custom-model.js │ ├── images │ │ ├── index.js │ │ ├── images-model.js │ │ └── hooks │ │ │ └── index.js │ ├── themes │ │ └── index.js │ ├── staging │ │ ├── index.js │ │ ├── staging-model.js │ │ └── hooks │ │ │ └── index.js │ ├── theme-uploads │ │ ├── index.js │ │ ├── theme-uploads.service.js │ │ └── hooks │ │ │ └── index.js │ ├── extensions │ │ ├── index.js │ │ └── extensions-model.js │ ├── import-export │ │ └── hooks │ │ │ └── index.js │ ├── image-uploads │ │ ├── image-uploads.service.js │ │ └── hooks │ │ │ └── index.js │ ├── extension-uploads │ │ ├── index.js │ │ ├── extension-uploads.service.js │ │ └── hooks │ │ │ └── index.js │ └── shared-content │ │ ├── index.js │ │ ├── shared-content-model.js │ │ └── hooks │ │ └── index.js ├── components │ ├── patterns │ │ └── index.js │ ├── settings │ │ └── index.js │ └── seed │ │ └── seed │ │ ├── user.js │ │ ├── menus.js │ │ ├── comments.js │ │ ├── extensions.js │ │ └── themes.js ├── routes.js └── app.js ├── test ├── services │ ├── ban │ │ └── index.test.js │ ├── email │ │ └── index.test.js │ ├── menus │ │ └── index.test.js │ ├── pages │ │ └── index.test.js │ ├── roles │ │ └── index.test.js │ ├── user │ │ └── index.test.js │ ├── custom │ │ └── index.test.js │ ├── images │ │ └── index.test.js │ ├── themes │ │ └── index.test.js │ ├── comments │ │ └── index.test.js │ ├── settings │ │ └── index.test.js │ ├── extension │ │ └── index.test.js │ ├── extensions │ │ └── index.test.js │ ├── subscribe │ │ └── index.test.js │ ├── import-export │ │ └── index.test.js │ └── shared-content │ │ └── index.test.js └── app.test.js ├── nginx ├── Dockerfile └── nginx.conf ├── docker-compose.dev.yml ├── nginx-prerender ├── Dockerfile └── nginx.conf ├── update.sh ├── e2e └── main │ ├── main.po.js │ └── main.spec.js ├── meanbase.dev.env ├── .editorconfig ├── Dockerfile.dev ├── gulp ├── document.js ├── test.js ├── admin │ └── bower.js └── app │ └── bower.js ├── .gitignore ├── Dockerfile ├── views └── admin.html ├── .yo-rc.json └── docker-compose.yml /blog: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto -------------------------------------------------------------------------------- /public/admin/code/import/import.styl: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/app/components/mb-link/mb-link.styl: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/extensions/mb-search/search-form.styl: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/themes/meanbase-demo/templates/blog/blog.styl: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/app/components/mb-choose-link/mb-choose-link.styl: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/themes/meanbase-demo/templates/archive/archive.styl: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/app/components/mb-list-area/mb-list-area.directive.spec.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/app/robots.txt: -------------------------------------------------------------------------------- 1 | # robotstxt.org 2 | 3 | User-agent: * 4 | -------------------------------------------------------------------------------- /public/shared/sortable/sortable.jade: -------------------------------------------------------------------------------- 1 | div this is the sortable directive -------------------------------------------------------------------------------- /public/extensions/mb-search/mb-search-box-extension.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /meanbase-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingfriend1/meanbase/HEAD/meanbase-logo.png -------------------------------------------------------------------------------- /Meanbase-Sidebar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingfriend1/meanbase/HEAD/Meanbase-Sidebar.png -------------------------------------------------------------------------------- /data/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore everything 2 | * 3 | 4 | # But not these files... 5 | !.gitignore 6 | -------------------------------------------------------------------------------- /Meanbase-Frontend.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingfriend1/meanbase/HEAD/Meanbase-Frontend.png -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /Meanbase-Themes-Page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingfriend1/meanbase/HEAD/Meanbase-Themes-Page.png -------------------------------------------------------------------------------- /meanbase-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingfriend1/meanbase/HEAD/meanbase-screenshot.png -------------------------------------------------------------------------------- /public/assets/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore everything in this directory 2 | * 3 | # Except this file 4 | !.gitignore 5 | -------------------------------------------------------------------------------- /export/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore everything 2 | * 3 | # But not these files... 4 | !.gitignore 5 | !export/ 6 | !import/ 7 | -------------------------------------------------------------------------------- /public/extensions/md-selling-point-list/selling-point-extension.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2017"], 3 | "plugins": ["transform-async-to-generator", "angularjs-annotate"] 4 | } 5 | -------------------------------------------------------------------------------- /public/app/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingfriend1/meanbase/HEAD/public/app/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /public/admin/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingfriend1/meanbase/HEAD/public/admin/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /public/admin/code/analytics/analytics.styl: -------------------------------------------------------------------------------- 1 | #meanbase-analytics 2 | padding-top 1em 3 | 4 | button 5 | margin-top 0.5em 6 | -------------------------------------------------------------------------------- /public/app/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingfriend1/meanbase/HEAD/public/app/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /public/app/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingfriend1/meanbase/HEAD/public/app/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /public/themes/meanbase-demo/img/dog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingfriend1/meanbase/HEAD/public/themes/meanbase-demo/img/dog.png -------------------------------------------------------------------------------- /public/themes/meanbase-demo/img/ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingfriend1/meanbase/HEAD/public/themes/meanbase-demo/img/ipad.png -------------------------------------------------------------------------------- /public/admin/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingfriend1/meanbase/HEAD/public/admin/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /public/admin/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingfriend1/meanbase/HEAD/public/admin/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /public/app/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingfriend1/meanbase/HEAD/public/app/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /public/app/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingfriend1/meanbase/HEAD/public/app/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /public/extensions/mb-search/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingfriend1/meanbase/HEAD/public/extensions/mb-search/screenshot.png -------------------------------------------------------------------------------- /public/themes/meanbase-demo/img/phones.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingfriend1/meanbase/HEAD/public/themes/meanbase-demo/img/phones.png -------------------------------------------------------------------------------- /public/themes/meanbase-demo/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingfriend1/meanbase/HEAD/public/themes/meanbase-demo/screenshot.png -------------------------------------------------------------------------------- /public/admin/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingfriend1/meanbase/HEAD/public/admin/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /public/admin/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingfriend1/meanbase/HEAD/public/admin/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /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/img/banner-bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingfriend1/meanbase/HEAD/public/themes/meanbase-demo/img/banner-bg.jpg -------------------------------------------------------------------------------- /public/themes/meanbase-demo/img/intro-bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingfriend1/meanbase/HEAD/public/themes/meanbase-demo/img/intro-bg.jpg -------------------------------------------------------------------------------- /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/meanbase-editable/icons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingfriend1/meanbase/HEAD/public/app/components/meanbase-editable/icons.png -------------------------------------------------------------------------------- /public/shared/missing/missing.controller.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | angular.module('meanbaseApp') 4 | .controller('MissingCtrl', function ($scope) { 5 | 6 | }); 7 | -------------------------------------------------------------------------------- /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/validate/validate.jade: -------------------------------------------------------------------------------- 1 | div 2 | ng-transclude 3 | div.help-block.required This field is required. 4 | div.help-block.error {{errorMessage}} -------------------------------------------------------------------------------- /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/meanbase-editable/icons-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingfriend1/meanbase/HEAD/public/app/components/meanbase-editable/icons-2x.png -------------------------------------------------------------------------------- /.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 -------------------------------------------------------------------------------- /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/themes/meanbase-demo/templates/blog/blog-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingfriend1/meanbase/HEAD/public/themes/meanbase-demo/templates/blog/blog-screenshot.png -------------------------------------------------------------------------------- /public/themes/meanbase-demo/templates/home/home-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingfriend1/meanbase/HEAD/public/themes/meanbase-demo/templates/home/home-screenshot.png -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /public/themes/meanbase-demo/templates/archive/archive-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingfriend1/meanbase/HEAD/public/themes/meanbase-demo/templates/archive/archive-screenshot.png -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/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/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/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 | -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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/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/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/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/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/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/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/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/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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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/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/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/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/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/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/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/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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /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/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/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/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/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/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/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/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/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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | }; -------------------------------------------------------------------------------- /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/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/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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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-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/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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /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/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/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/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/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/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/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/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/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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /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/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-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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/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) -------------------------------------------------------------------------------- /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/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/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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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/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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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/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/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-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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | }; -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/extensions/md-selling-point-list/toggle-type.modal.html: -------------------------------------------------------------------------------- 1 |
2 | 6 | 15 | 19 |
20 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /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/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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/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/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/extensions/md-bullet-point-list/bullet-point-list-extension.html: -------------------------------------------------------------------------------- 1 |
2 | 21 |
22 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /.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 | } -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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-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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/admin/code/cms/cms.jade: -------------------------------------------------------------------------------- 1 | #meanbase-cms.mdl-layout.mdl-js-layout.mdl-layout--fixed-header.mdl-layout--fixed-tabs 2 | header.mdl-layout__header 3 | .mdl-layout__header-row 4 | span.mdl-layout-title {{pageTitle}} 5 | .mdl-layout-spacer 6 | .mdl-textfield.mdl-js-textfield.mdl-textfield--expandable.mdl-textfield--floating-label.mdl-textfield--align-right 7 | label.mdl-button.mdl-js-button.mdl-button--icon(for='fixed-header-drawer-exp') 8 | i.material-icons search 9 | .mdl-textfield__expandable-holder 10 | input#fixed-header-drawer-exp.mdl-textfield__input(type='text', ng-model="$root.searchText") 11 | .mdl-layout__tab-bar.mdl-js-ripple-effect(ng-if="tabs" mdl) 12 | a.mdl-layout__tab(ng-click='goToTab(tab)' ng-repeat="tab in tabs" ng-class="{'is-active': tab.active}") {{tab.title}} 13 | .mdl-layout__drawer 14 | nav.mdl-navigation 15 | li.mdl-list__item(ng-click="goToApp()") 16 | span.mdl-list__item-primary-content 17 | i.material-icons.mdl-list__item-icon arrow_back 18 | | Back to Site 19 | li.mdl-list__item(ng-repeat="state in cmsStates" ui-sref='{{state.name}}' ng-show="state.userHasPermission && !state.hide") 20 | span.mdl-list__item-primary-content 21 | i.material-icons.mdl-list__item-icon {{state.icon}} 22 | | {{state.friendlyName}} 23 | main.mdl-layout__content 24 | .page-content 25 | div(ui-view="") 26 | .mdl-grid 27 | .mdl-cell.mdl-cell--12-col 28 | h1 Welcome to meanbase CMS 29 | p I'm sure your excited and maybe nervous to begin managing your website. Don't worry we've made the process easy to follow. Just start clicking buttons. Just be careful on the theme's page, that could mess up your site. 30 | --------------------------------------------------------------------------------