├── core
├── test
│ ├── utils
│ │ └── fixtures
│ │ │ ├── test.hbs
│ │ │ ├── theme
│ │ │ └── partials
│ │ │ │ └── test.hbs
│ │ │ ├── example.js
│ │ │ └── app
│ │ │ ├── goodlib.js
│ │ │ ├── badlib.js
│ │ │ ├── nested
│ │ │ └── goodnested.js
│ │ │ ├── badoutside.js
│ │ │ ├── badtop.js
│ │ │ ├── badinstall.js
│ │ │ ├── badrequire.js
│ │ │ └── good.js
│ ├── blanket_coverage.js
│ ├── functional
│ │ ├── frontend
│ │ │ ├── route_test.js
│ │ │ ├── error_test.js
│ │ │ └── post_test.js
│ │ └── client
│ │ │ ├── signout_test.js
│ │ │ └── signup_test.js
│ ├── unit
│ │ ├── server_spec.js
│ │ └── xmlrpc_spec.js
│ └── integration
│ │ ├── api
│ │ └── api_mail_spec.js
│ │ └── model
│ │ ├── model_app_fields_spec.js
│ │ └── model_app_settings_spec.js
├── shared
│ ├── robots.txt
│ ├── img
│ │ ├── user-cover.png
│ │ └── user-image.png
│ ├── lang
│ │ ├── en_US.json
│ │ └── i18n.js
│ └── favicon.ico
├── client
│ ├── templates
│ │ ├── components
│ │ │ ├── gh-markdown.hbs
│ │ │ ├── gh-notifications.hbs
│ │ │ ├── gh-activating-list-item.hbs
│ │ │ ├── gh-role-selector.hbs
│ │ │ ├── gh-notification.hbs
│ │ │ ├── gh-file-upload.hbs
│ │ │ └── gh-modal-dialog.hbs
│ │ ├── -import-errors.hbs
│ │ ├── settings
│ │ │ ├── users.hbs
│ │ │ └── apps.hbs
│ │ ├── posts
│ │ │ ├── post.hbs
│ │ │ └── index.hbs
│ │ ├── user-actions-menu.hbs
│ │ ├── modals
│ │ │ ├── transfer-owner.hbs
│ │ │ ├── delete-all.hbs
│ │ │ ├── delete-post.hbs
│ │ │ ├── delete-user.hbs
│ │ │ ├── leave-editor.hbs
│ │ │ ├── upload.hbs
│ │ │ └── invite-new-user.hbs
│ │ ├── application.hbs
│ │ ├── forgotten.hbs
│ │ ├── settings.hbs
│ │ ├── post-tags-input.hbs
│ │ ├── reset.hbs
│ │ ├── -publish-bar.hbs
│ │ ├── editor-save-button.hbs
│ │ ├── signin.hbs
│ │ ├── error.hbs
│ │ ├── -floating-header.hbs
│ │ ├── editor
│ │ │ └── edit.hbs
│ │ ├── posts.hbs
│ │ ├── signup.hbs
│ │ └── post-settings-menu.hbs
│ ├── utils
│ │ ├── text-field.js
│ │ ├── link-view.js
│ │ ├── word-count.js
│ │ ├── validator-extensions.js
│ │ ├── mobile.js
│ │ ├── titleize.js
│ │ ├── caja-sanitizers.js
│ │ ├── set-scroll-classname.js
│ │ ├── bound-one-way.js
│ │ ├── date-formatting.js
│ │ ├── ghost-paths.js
│ │ ├── ajax.js
│ │ └── codemirror-mobile.js
│ ├── assets
│ │ ├── img
│ │ │ ├── large.png
│ │ │ ├── medium.png
│ │ │ ├── small.png
│ │ │ ├── 404-ghost.png
│ │ │ ├── loadingcat.gif
│ │ │ ├── 404-ghost@2x.png
│ │ │ ├── touch-icon-ipad.png
│ │ │ └── touch-icon-iphone.png
│ │ └── lib
│ │ │ └── touch-editor.js
│ ├── validators
│ │ ├── signup.js
│ │ ├── post.js
│ │ ├── forgotten.js
│ │ ├── setup.js
│ │ ├── signin.js
│ │ ├── reset.js
│ │ ├── new-user.js
│ │ └── setting.js
│ ├── loader.js
│ ├── config-prod.js
│ ├── views
│ │ ├── settings
│ │ │ ├── apps.js
│ │ │ ├── users.js
│ │ │ ├── general.js
│ │ │ ├── index.js
│ │ │ ├── users
│ │ │ │ ├── users-list-view.js
│ │ │ │ └── user.js
│ │ │ └── content-base.js
│ │ ├── editor
│ │ │ ├── edit.js
│ │ │ └── new.js
│ │ ├── item-view.js
│ │ ├── post-settings-menu-view.js
│ │ ├── post-item-view.js
│ │ ├── content-list-content-view.js
│ │ ├── content-preview-content-view.js
│ │ ├── application.js
│ │ ├── editor-save-button.js
│ │ ├── posts.js
│ │ └── settings.js
│ ├── controllers
│ │ ├── settings.js
│ │ ├── editor
│ │ │ ├── edit.js
│ │ │ └── new.js
│ │ ├── application.js
│ │ ├── error.js
│ │ ├── posts
│ │ │ └── post.js
│ │ ├── modals
│ │ │ ├── upload.js
│ │ │ ├── delete-all.js
│ │ │ ├── delete-user.js
│ │ │ ├── delete-post.js
│ │ │ └── transfer-owner.js
│ │ ├── settings
│ │ │ ├── users
│ │ │ │ └── index.js
│ │ │ ├── general.js
│ │ │ └── app.js
│ │ ├── signin.js
│ │ ├── forgotten.js
│ │ ├── reset.js
│ │ └── setup.js
│ ├── routes
│ │ ├── content.js
│ │ ├── editor
│ │ │ └── index.js
│ │ ├── error404.js
│ │ ├── forgotten.js
│ │ ├── settings.js
│ │ ├── settings
│ │ │ ├── users.js
│ │ │ ├── apps.js
│ │ │ ├── general.js
│ │ │ ├── users
│ │ │ │ ├── index.js
│ │ │ │ └── user.js
│ │ │ └── index.js
│ │ ├── signout.js
│ │ ├── reset.js
│ │ ├── debug.js
│ │ ├── signin.js
│ │ ├── setup.js
│ │ ├── signup.js
│ │ └── posts
│ │ │ └── index.js
│ ├── helpers
│ │ ├── gh-blog-url.js
│ │ ├── gh-count-words.js
│ │ ├── gh-format-timeago.js
│ │ ├── gh-count-characters.js
│ │ ├── gh-format-html.js
│ │ └── gh-format-markdown.js
│ ├── components
│ │ ├── gh-activating-list-item.js
│ │ ├── gh-trim-focus-input.js
│ │ ├── gh-form.js
│ │ ├── gh-role-selector.js
│ │ ├── gh-popover-button.js
│ │ ├── gh-file-upload.js
│ │ ├── gh-blur-input.js
│ │ ├── gh-notifications.js
│ │ ├── gh-markdown.js
│ │ ├── gh-notification.js
│ │ ├── gh-upload-modal.js
│ │ └── gh-modal-dialog.js
│ ├── models
│ │ ├── notification.js
│ │ ├── tag.js
│ │ ├── role.js
│ │ ├── setting.js
│ │ └── slug-generator.js
│ ├── initializers
│ │ ├── store-injector.js
│ │ ├── trailing-history.js
│ │ ├── ghost-paths.js
│ │ ├── ghost-config.js
│ │ ├── notifications.js
│ │ ├── authentication.js
│ │ └── popover.js
│ ├── mixins
│ │ ├── popover-mixin.js
│ │ ├── nprogress-save.js
│ │ ├── pagination-route.js
│ │ ├── loading-indicator.js
│ │ ├── current-user-settings.js
│ │ ├── style-body.js
│ │ ├── body-event-listener.js
│ │ └── pagination-view-infinite-scroll.js
│ ├── config-dev.js
│ ├── transforms
│ │ └── moment-date.js
│ ├── app.js
│ ├── serializers
│ │ ├── application.js
│ │ ├── setting.js
│ │ └── user.js
│ ├── adapters
│ │ ├── setting.js
│ │ ├── application.js
│ │ ├── post.js
│ │ └── user.js
│ ├── .jshintrc
│ └── router.js
├── server
│ ├── data
│ │ ├── import
│ │ │ ├── 001.js
│ │ │ ├── 002.js
│ │ │ └── 003.js
│ │ └── utils
│ │ │ └── clients
│ │ │ ├── index.js
│ │ │ ├── sqlite3.js
│ │ │ └── pg.js
│ ├── helpers
│ │ ├── tpl
│ │ │ ├── nav.hbs
│ │ │ └── pagination.hbs
│ │ └── template.js
│ ├── routes
│ │ ├── index.js
│ │ ├── admin.js
│ │ └── frontend.js
│ ├── permissions
│ │ ├── objectTypeModelMap.js
│ │ └── effective.js
│ ├── errors
│ │ ├── emailerror.js
│ │ ├── notfounderror.js
│ │ ├── badrequesterror.js
│ │ ├── nopermissionerror.js
│ │ ├── unauthorizederror.js
│ │ ├── internalservererror.js
│ │ ├── unsupportedmediaerror.js
│ │ ├── requesttoolargeerror.js
│ │ ├── validationerror.js
│ │ └── dataimporterror.js
│ ├── models
│ │ ├── client.js
│ │ ├── accesstoken.js
│ │ ├── refreshtoken.js
│ │ ├── appField.js
│ │ ├── appSetting.js
│ │ ├── permission.js
│ │ ├── basetoken.js
│ │ ├── tag.js
│ │ └── index.js
│ ├── storage
│ │ ├── index.js
│ │ └── base.js
│ ├── api.js
│ ├── middleware.js
│ ├── api
│ │ ├── tags.js
│ │ └── utils.js
│ ├── views
│ │ └── error.hbs
│ ├── config
│ │ └── theme.js
│ ├── apps
│ │ └── dependencies.js
│ └── controllers
│ │ └── admin.js
├── server.js
└── index.js
├── .bowerrc
├── content
├── apps
│ └── README.md
├── images
│ └── README.md
└── data
│ └── README.md
├── .gitmodules
├── index.js
├── SECURITY.md
├── .jshintrc
├── .npmignore
├── .travis.yml
├── .gitignore
├── LICENSE
└── bower.json
/core/test/utils/fixtures/test.hbs:
--------------------------------------------------------------------------------
1 |
HelloWorld
--------------------------------------------------------------------------------
/.bowerrc:
--------------------------------------------------------------------------------
1 | {
2 | "directory": "bower_components"
3 | }
--------------------------------------------------------------------------------
/core/shared/robots.txt:
--------------------------------------------------------------------------------
1 | User-agent: *
2 | Disallow: /ghost/
--------------------------------------------------------------------------------
/core/test/utils/fixtures/theme/partials/test.hbs:
--------------------------------------------------------------------------------
1 | HelloWorld Themed
--------------------------------------------------------------------------------
/core/test/utils/fixtures/example.js:
--------------------------------------------------------------------------------
1 |
2 | module.exports = {
3 | answer: 42
4 | };
--------------------------------------------------------------------------------
/core/client/templates/components/gh-markdown.hbs:
--------------------------------------------------------------------------------
1 | {{gh-format-markdown markdown}}
2 |
--------------------------------------------------------------------------------
/content/apps/README.md:
--------------------------------------------------------------------------------
1 | # Content / Apps
2 |
3 | Coming soon, Ghost apps will appear here.
--------------------------------------------------------------------------------
/core/client/utils/text-field.js:
--------------------------------------------------------------------------------
1 | Ember.TextField.reopen({
2 | attributeBindings: ['autofocus']
3 | });
4 |
--------------------------------------------------------------------------------
/core/shared/img/user-cover.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bigertech/Ghost/HEAD/core/shared/img/user-cover.png
--------------------------------------------------------------------------------
/core/shared/img/user-image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bigertech/Ghost/HEAD/core/shared/img/user-image.png
--------------------------------------------------------------------------------
/core/client/assets/img/large.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bigertech/Ghost/HEAD/core/client/assets/img/large.png
--------------------------------------------------------------------------------
/core/client/assets/img/medium.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bigertech/Ghost/HEAD/core/client/assets/img/medium.png
--------------------------------------------------------------------------------
/core/client/assets/img/small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bigertech/Ghost/HEAD/core/client/assets/img/small.png
--------------------------------------------------------------------------------
/core/test/utils/fixtures/app/goodlib.js:
--------------------------------------------------------------------------------
1 |
2 | module.exports = {
3 | util: function () {
4 | return 42;
5 | }
6 | };
--------------------------------------------------------------------------------
/core/client/assets/img/404-ghost.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bigertech/Ghost/HEAD/core/client/assets/img/404-ghost.png
--------------------------------------------------------------------------------
/core/test/utils/fixtures/app/badlib.js:
--------------------------------------------------------------------------------
1 |
2 | var knex = require('knex');
3 |
4 | module.exports = {
5 | knex: knex
6 | };
--------------------------------------------------------------------------------
/core/client/assets/img/loadingcat.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bigertech/Ghost/HEAD/core/client/assets/img/loadingcat.gif
--------------------------------------------------------------------------------
/content/images/README.md:
--------------------------------------------------------------------------------
1 | # Content / Images
2 |
3 | If using the standard file storage, Ghost will upload images to this directory.
--------------------------------------------------------------------------------
/core/client/assets/img/404-ghost@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bigertech/Ghost/HEAD/core/client/assets/img/404-ghost@2x.png
--------------------------------------------------------------------------------
/core/client/templates/components/gh-notifications.hbs:
--------------------------------------------------------------------------------
1 | {{#each messages}}
2 | {{gh-notification message=this}}
3 | {{/each}}
4 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "content/themes/casper"]
2 | path = content/themes/casper
3 | url = https://github.com/TryGhost/Casper.git
4 |
--------------------------------------------------------------------------------
/core/client/assets/img/touch-icon-ipad.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bigertech/Ghost/HEAD/core/client/assets/img/touch-icon-ipad.png
--------------------------------------------------------------------------------
/core/client/templates/components/gh-activating-list-item.hbs:
--------------------------------------------------------------------------------
1 | {{#link-to route alternateActive=active}}{{title}}{{yield}}{{/link-to}}
2 |
--------------------------------------------------------------------------------
/core/client/assets/img/touch-icon-iphone.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bigertech/Ghost/HEAD/core/client/assets/img/touch-icon-iphone.png
--------------------------------------------------------------------------------
/core/test/utils/fixtures/app/nested/goodnested.js:
--------------------------------------------------------------------------------
1 |
2 | var lib = require('../goodlib.js');
3 |
4 | module.exports = {
5 | other: 42
6 | };
--------------------------------------------------------------------------------
/core/client/validators/signup.js:
--------------------------------------------------------------------------------
1 | import NewUserValidator from 'ghost/validators/new-user';
2 |
3 | export default NewUserValidator.create();
4 |
--------------------------------------------------------------------------------
/content/data/README.md:
--------------------------------------------------------------------------------
1 | # Content / Data
2 |
3 | This is the home of your Ghost database, do not overwrite this folder or any of the files inside of it.
--------------------------------------------------------------------------------
/core/client/loader.js:
--------------------------------------------------------------------------------
1 | // Loader to create the Ember.js application
2 | /*global require */
3 |
4 | window.App = require('ghost/app')['default'].create();
--------------------------------------------------------------------------------
/core/client/config-prod.js:
--------------------------------------------------------------------------------
1 | function configureApp(App) {
2 | if (!App instanceof Ember.Application) {
3 | return;
4 | }
5 | }
6 |
7 | export default configureApp;
8 |
--------------------------------------------------------------------------------
/core/client/templates/-import-errors.hbs:
--------------------------------------------------------------------------------
1 | {{#if importErrors}}
2 |
3 | {{#each importErrors}}
4 | | {{message}} |
5 | {{/each}}
6 |
7 | {{/if}}
--------------------------------------------------------------------------------
/core/client/views/settings/apps.js:
--------------------------------------------------------------------------------
1 | import BaseView from 'ghost/views/settings/content-base';
2 |
3 | var SettingsAppsView = BaseView.extend();
4 |
5 | export default SettingsAppsView;
6 |
--------------------------------------------------------------------------------
/core/client/views/settings/users.js:
--------------------------------------------------------------------------------
1 | import BaseView from 'ghost/views/settings/content-base';
2 |
3 | var SettingsUsersView = BaseView.extend();
4 |
5 | export default SettingsUsersView;
6 |
--------------------------------------------------------------------------------
/core/client/controllers/settings.js:
--------------------------------------------------------------------------------
1 | var SettingsController = Ember.Controller.extend({
2 | showApps: Ember.computed.bool('config.apps')
3 | });
4 |
5 | export default SettingsController;
6 |
--------------------------------------------------------------------------------
/core/client/views/settings/general.js:
--------------------------------------------------------------------------------
1 | import BaseView from 'ghost/views/settings/content-base';
2 |
3 | var SettingsGeneralView = BaseView.extend();
4 |
5 | export default SettingsGeneralView;
6 |
--------------------------------------------------------------------------------
/core/client/routes/content.js:
--------------------------------------------------------------------------------
1 | var ContentRoute = Ember.Route.extend({
2 | beforeModel: function () {
3 | this.transitionTo('posts');
4 | }
5 | });
6 |
7 | export default ContentRoute;
--------------------------------------------------------------------------------
/core/client/routes/editor/index.js:
--------------------------------------------------------------------------------
1 | var EditorRoute = Ember.Route.extend({
2 | beforeModel: function () {
3 | this.transitionTo('editor.new');
4 | }
5 | });
6 |
7 | export default EditorRoute;
8 |
--------------------------------------------------------------------------------
/core/client/templates/components/gh-role-selector.hbs:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/core/client/helpers/gh-blog-url.js:
--------------------------------------------------------------------------------
1 | var blogUrl = Ember.Handlebars.makeBoundHelper(function () {
2 |
3 | return new Ember.Handlebars.SafeString(this.get('config.blogUrl'));
4 | });
5 |
6 | export default blogUrl;
--------------------------------------------------------------------------------
/core/client/templates/settings/users.hbs:
--------------------------------------------------------------------------------
1 | {{!
2 | Yes, this is the template default,
3 | but for some reason things break without it in this instance.
4 | @TODO Find a better fix?
5 | }}
6 | {{outlet}}
7 |
--------------------------------------------------------------------------------
/core/server/data/import/001.js:
--------------------------------------------------------------------------------
1 | var Importer000 = require('./000');
2 |
3 | module.exports = {
4 | Importer001: Importer000,
5 | importData: function (data) {
6 | return new Importer000.importData(data);
7 | }
8 | };
--------------------------------------------------------------------------------
/core/server/data/import/002.js:
--------------------------------------------------------------------------------
1 | var Importer000 = require('./000');
2 |
3 | module.exports = {
4 | Importer002: Importer000,
5 | importData: function (data) {
6 | return new Importer000.importData(data);
7 | }
8 | };
--------------------------------------------------------------------------------
/core/server/data/import/003.js:
--------------------------------------------------------------------------------
1 | var Importer000 = require('./000');
2 |
3 | module.exports = {
4 | Importer003: Importer000,
5 | importData: function (data) {
6 | return new Importer000.importData(data);
7 | }
8 | };
--------------------------------------------------------------------------------
/core/client/components/gh-activating-list-item.js:
--------------------------------------------------------------------------------
1 | var ActivatingListItem = Ember.Component.extend({
2 | tagName: 'li',
3 | classNameBindings: ['active'],
4 | active: false
5 | });
6 |
7 | export default ActivatingListItem;
8 |
--------------------------------------------------------------------------------
/core/client/controllers/editor/edit.js:
--------------------------------------------------------------------------------
1 | import EditorControllerMixin from 'ghost/mixins/editor-base-controller';
2 |
3 | var EditorEditController = Ember.ObjectController.extend(EditorControllerMixin);
4 |
5 | export default EditorEditController;
6 |
--------------------------------------------------------------------------------
/core/server/helpers/tpl/nav.hbs:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/core/client/templates/posts/post.hbs:
--------------------------------------------------------------------------------
1 | {{partial "floating-header"}}
2 |
3 | {{#view "content-preview-content-view" tagName="section"}}
4 |
5 |
{{title}}
6 | {{gh-format-html html}}
7 |
8 | {{/view}}
9 |
--------------------------------------------------------------------------------
/core/server/data/utils/clients/index.js:
--------------------------------------------------------------------------------
1 | var sqlite3 = require('./sqlite3'),
2 | mysql = require('./mysql'),
3 | pg = require('./pg');
4 |
5 |
6 | module.exports = {
7 | sqlite3: sqlite3,
8 | mysql: mysql,
9 | pg: pg
10 | };
11 |
--------------------------------------------------------------------------------
/core/client/views/editor/edit.js:
--------------------------------------------------------------------------------
1 | import EditorViewMixin from 'ghost/mixins/editor-base-view';
2 |
3 | var EditorView = Ember.View.extend(EditorViewMixin, {
4 | tagName: 'section',
5 | classNames: ['entry-container']
6 | });
7 |
8 | export default EditorView;
9 |
--------------------------------------------------------------------------------
/core/client/routes/error404.js:
--------------------------------------------------------------------------------
1 | var Error404Route = Ember.Route.extend({
2 | controllerName: 'error',
3 | templateName: 'error',
4 |
5 | model: function () {
6 | return {
7 | status: 404
8 | };
9 | }
10 | });
11 |
12 | export default Error404Route;
--------------------------------------------------------------------------------
/core/client/utils/link-view.js:
--------------------------------------------------------------------------------
1 | Ember.LinkView.reopen({
2 | active: Ember.computed('resolvedParams', 'routeArgs', function () {
3 | var isActive = this._super();
4 |
5 | Ember.set(this, 'alternateActive', isActive);
6 |
7 | return isActive;
8 | })
9 | });
10 |
--------------------------------------------------------------------------------
/core/server/routes/index.js:
--------------------------------------------------------------------------------
1 | var api = require('./api'),
2 | admin = require('./admin'),
3 | frontend = require('./frontend');
4 |
5 | module.exports = {
6 | apiBaseUri: '/ghost/api/v0.1/',
7 | api: api,
8 | admin: admin,
9 | frontend: frontend
10 | };
--------------------------------------------------------------------------------
/core/client/models/notification.js:
--------------------------------------------------------------------------------
1 | var Notification = DS.Model.extend({
2 | dismissible: DS.attr('boolean'),
3 | location: DS.attr('string'),
4 | status: DS.attr('string'),
5 | type: DS.attr('string'),
6 | message: DS.attr('string')
7 | });
8 |
9 | export default Notification;
10 |
--------------------------------------------------------------------------------
/core/client/views/item-view.js:
--------------------------------------------------------------------------------
1 | var ItemView = Ember.View.extend({
2 | classNameBindings: ['active'],
3 |
4 | active: function () {
5 | return this.get('childViews.firstObject.active');
6 | }.property('childViews.firstObject.active')
7 | });
8 |
9 | export default ItemView;
10 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | // # Ghost bootloader
2 | // Orchestrates the loading of Ghost
3 | // When run from command line.
4 |
5 | var ghost = require('./core'),
6 | errors = require('./core/server/errors');
7 |
8 | ghost().otherwise(function (err) {
9 | errors.logErrorAndExit(err, err.context, err.help);
10 | });
--------------------------------------------------------------------------------
/core/client/routes/forgotten.js:
--------------------------------------------------------------------------------
1 | import styleBody from 'ghost/mixins/style-body';
2 | import loadingIndicator from 'ghost/mixins/loading-indicator';
3 |
4 | var ForgottenRoute = Ember.Route.extend(styleBody, loadingIndicator, {
5 | classNames: ['ghost-forgotten']
6 | });
7 |
8 | export default ForgottenRoute;
9 |
--------------------------------------------------------------------------------
/core/client/templates/components/gh-notification.hbs:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{{message.message}}}
4 |
5 |
6 |
--------------------------------------------------------------------------------
/core/client/templates/posts/index.hbs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
You Haven't Written Any Posts Yet!
4 | {{#link-to "editor.new"}}{{/link-to}}
5 |
6 |
7 |
--------------------------------------------------------------------------------
/core/client/views/editor/new.js:
--------------------------------------------------------------------------------
1 | import EditorViewMixin from 'ghost/mixins/editor-base-view';
2 |
3 | var EditorNewView = Ember.View.extend(EditorViewMixin, {
4 | tagName: 'section',
5 | templateName: 'editor/edit',
6 | classNames: ['entry-container']
7 | });
8 |
9 | export default EditorNewView;
10 |
--------------------------------------------------------------------------------
/core/client/views/settings/index.js:
--------------------------------------------------------------------------------
1 | var SettingsIndexView = Ember.View.extend({
2 | //Ensure that going to the index brings the menu into view on mobile.
3 | showMenu: function () {
4 | this.get('parentView').showSettingsMenu();
5 | }.on('didInsertElement')
6 | });
7 |
8 | export default SettingsIndexView;
9 |
--------------------------------------------------------------------------------
/core/test/utils/fixtures/app/badoutside.js:
--------------------------------------------------------------------------------
1 |
2 | var lib = require('../example');
3 |
4 | function BadApp(app) {
5 | this.app = app;
6 | }
7 |
8 | BadApp.prototype.install = function () {
9 | return lib.answer;
10 | };
11 |
12 | BadApp.prototype.activate = function () {
13 |
14 | };
15 |
16 | module.exports = BadApp;
--------------------------------------------------------------------------------
/core/client/routes/settings.js:
--------------------------------------------------------------------------------
1 | import styleBody from 'ghost/mixins/style-body';
2 | import loadingIndicator from 'ghost/mixins/loading-indicator';
3 |
4 | var SettingsRoute = Ember.Route.extend(SimpleAuth.AuthenticatedRouteMixin, styleBody, loadingIndicator, {
5 | classNames: ['settings']
6 | });
7 |
8 | export default SettingsRoute;
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Reporting Security Issues
2 |
3 | If you discover a security issue in Ghost, please report it by sending an email to security[at]ghost[dot]org
4 |
5 | This will allow us to assess the risk, and make a fix available before we add a bug report to the GitHub repository.
6 |
7 | Thanks for helping make Ghost safe for everyone.
8 |
--------------------------------------------------------------------------------
/core/test/utils/fixtures/app/badtop.js:
--------------------------------------------------------------------------------
1 |
2 | var knex = require('knex');
3 |
4 | function BadApp(app) {
5 | this.app = app;
6 | }
7 |
8 | BadApp.prototype.install = function () {
9 | return knex.dropTableIfExists('users');
10 | };
11 |
12 | BadApp.prototype.activate = function () {
13 |
14 | };
15 |
16 | module.exports = BadApp;
--------------------------------------------------------------------------------
/core/client/templates/user-actions-menu.hbs:
--------------------------------------------------------------------------------
1 | {{#if view.parentView.canMakeOwner}}
2 | Make Owner
3 | {{/if}}
4 | {{#if view.parentView.deleteUserActionIsVisible}}
5 | Delete User
6 | {{/if}}
--------------------------------------------------------------------------------
/core/test/utils/fixtures/app/badinstall.js:
--------------------------------------------------------------------------------
1 |
2 | function BadApp(app) {
3 | this.app = app;
4 | }
5 |
6 | BadApp.prototype.install = function () {
7 | var knex = require('knex');
8 |
9 | return knex.dropTableIfExists('users');
10 | };
11 |
12 | BadApp.prototype.activate = function () {
13 |
14 | };
15 |
16 | module.exports = BadApp;
--------------------------------------------------------------------------------
/core/client/models/tag.js:
--------------------------------------------------------------------------------
1 | var Tag = DS.Model.extend({
2 | uuid: DS.attr('string'),
3 | name: DS.attr('string'),
4 | slug: DS.attr('string'),
5 | description: DS.attr('string'),
6 | parent_id: DS.attr('number'),
7 | meta_title: DS.attr('string'),
8 | meta_description: DS.attr('string'),
9 | });
10 |
11 | export default Tag;
--------------------------------------------------------------------------------
/core/client/templates/components/gh-file-upload.hbs:
--------------------------------------------------------------------------------
1 |
2 |
5 |
--------------------------------------------------------------------------------
/core/test/utils/fixtures/app/badrequire.js:
--------------------------------------------------------------------------------
1 |
2 | var lib = require('./badlib');
3 |
4 | function BadApp(app) {
5 | this.app = app;
6 | }
7 |
8 | BadApp.prototype.install = function () {
9 | return lib.knex.dropTableIfExists('users');
10 | };
11 |
12 | BadApp.prototype.activate = function () {
13 |
14 | };
15 |
16 | module.exports = BadApp;
--------------------------------------------------------------------------------
/core/client/templates/modals/transfer-owner.hbs:
--------------------------------------------------------------------------------
1 | {{#gh-modal-dialog action="closeModal" showClose=true type="action" style="wide,centered" animation="fade"
2 | title="Transfer Ownership" confirm=confirm}}
3 |
4 | Are you sure you want to transfer the ownership of this blog? You will not be able to undo this action.
5 |
6 | {{/gh-modal-dialog}}
7 |
--------------------------------------------------------------------------------
/core/client/views/settings/users/users-list-view.js:
--------------------------------------------------------------------------------
1 | //import setScrollClassName from 'ghost/utils/set-scroll-classname';
2 | import PaginationViewMixin from 'ghost/mixins/pagination-view-infinite-scroll';
3 |
4 | var UsersListView = Ember.View.extend(PaginationViewMixin, {
5 | classNames: ['settings-users']
6 | });
7 |
8 | export default UsersListView;
9 |
--------------------------------------------------------------------------------
/core/server/permissions/objectTypeModelMap.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | 'post': require('../models/post').Post,
3 | 'role': require('../models/role').Role,
4 | 'user': require('../models/user').User,
5 | 'permission': require('../models/permission').Permission,
6 | 'setting': require('../models/settings').Settings
7 | };
8 |
--------------------------------------------------------------------------------
/core/client/utils/word-count.js:
--------------------------------------------------------------------------------
1 | export default function (s) {
2 | s = s.replace(/(^\s*)|(\s*$)/gi, ''); // exclude start and end white-space
3 | s = s.replace(/[ ]{2,}/gi, ' '); // 2 or more space to 1
4 | s = s.replace(/\n /gi, '\n'); // exclude newline with a start spacing
5 | s = s.replace(/\n+/gi, '\n');
6 | return s.split(/ |\n/).length;
7 | }
--------------------------------------------------------------------------------
/core/client/templates/modals/delete-all.hbs:
--------------------------------------------------------------------------------
1 | {{#gh-modal-dialog action="closeModal" type="action" style="wide,centered" animation="fade"
2 | title="Would you really like to delete all content from your blog?" confirm=confirm}}
3 |
4 | This is permanent! No backups, no restores, no magic undo button.
We warned you, ok?
5 |
6 | {{/gh-modal-dialog}}
7 |
--------------------------------------------------------------------------------
/core/client/templates/modals/delete-post.hbs:
--------------------------------------------------------------------------------
1 | {{#gh-modal-dialog action="closeModal" showClose=true type="action" style="wide,centered" animation="fade"
2 | title="Are you sure you want to delete this post?" confirm=confirm}}
3 |
4 | This is permanent! No backups, no restores, no magic undo button.
We warned you, ok?
5 |
6 | {{/gh-modal-dialog}}
7 |
--------------------------------------------------------------------------------
/core/client/templates/modals/delete-user.hbs:
--------------------------------------------------------------------------------
1 | {{#gh-modal-dialog action="closeModal" showClose=true type="action" style="wide,centered" animation="fade"
2 | title="Are you sure you want to delete this user?" confirm=confirm}}
3 |
4 | All posts and associated data will also be deleted. There is no way to recover this data.
5 |
6 |
7 | {{/gh-modal-dialog}}
--------------------------------------------------------------------------------
/core/client/templates/application.hbs:
--------------------------------------------------------------------------------
1 | {{#unless hideNav}}
2 | {{partial "navbar"}}
3 | {{/unless}}
4 |
5 |
6 | {{gh-notifications location="top" notify="topNotificationChange"}}
7 | {{gh-notifications location="bottom"}}
8 |
9 | {{outlet}}
10 |
11 | {{outlet modal}}
12 |
--------------------------------------------------------------------------------
/core/client/routes/settings/users.js:
--------------------------------------------------------------------------------
1 | import CurrentUserSettings from 'ghost/mixins/current-user-settings';
2 |
3 | var UsersRoute = Ember.Route.extend(SimpleAuth.AuthenticatedRouteMixin, CurrentUserSettings, {
4 | beforeModel: function () {
5 | return this.currentUser()
6 | .then(this.transitionAuthor());
7 | }
8 | });
9 |
10 | export default UsersRoute;
11 |
--------------------------------------------------------------------------------
/core/server/helpers/tpl/pagination.hbs:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/core/client/helpers/gh-count-words.js:
--------------------------------------------------------------------------------
1 | import counter from 'ghost/utils/word-count';
2 |
3 | var countWords = Ember.Handlebars.makeBoundHelper(function (markdown) {
4 | if (/^\s*$/.test(markdown)) {
5 | return '0 words';
6 | }
7 |
8 | var count = counter(markdown || '');
9 | return count + (count === 1 ? ' word' : ' words');
10 | });
11 |
12 | export default countWords;
--------------------------------------------------------------------------------
/core/test/blanket_coverage.js:
--------------------------------------------------------------------------------
1 | // Posts
2 | var blanket = require('blanket')({
3 | 'pattern': ['/core/server/', '/core/client/', '/core/shared/'],
4 | 'data-cover-only': ['/core/server/', '/core/client/', '/core/shared/']
5 | }),
6 | requireDir = require('require-dir');
7 |
8 |
9 | requireDir('./unit');
10 | requireDir('./integration');
11 | requireDir('./functional/routes');
12 |
--------------------------------------------------------------------------------
/core/client/initializers/store-injector.js:
--------------------------------------------------------------------------------
1 | //Used to surgically insert the store into things that wouldn't normally have them.
2 | var StoreInjector = {
3 | name: 'store-injector',
4 | after: 'store',
5 | initialize: function (container, application) {
6 | application.inject('component:gh-role-selector', 'store', 'store:main');
7 | }
8 | };
9 |
10 | export default StoreInjector;
11 |
--------------------------------------------------------------------------------
/core/client/mixins/popover-mixin.js:
--------------------------------------------------------------------------------
1 | /*
2 | Popovers and their buttons are evented and do not propagate clicks.
3 | */
4 | var PopoverMixin = Ember.Mixin.create(Ember.Evented, {
5 | classNameBindings: ['isOpen:open'],
6 | isOpen: false,
7 | click: function (event) {
8 | this._super(event);
9 | return event.stopPropagation();
10 | }
11 | });
12 |
13 | export default PopoverMixin;
--------------------------------------------------------------------------------
/core/client/utils/validator-extensions.js:
--------------------------------------------------------------------------------
1 | function init() {
2 | // Provide a few custom validators
3 | //
4 | validator.extend('empty', function (str) {
5 | return Ember.isBlank(str);
6 | });
7 |
8 | validator.extend('notContains', function (str, badString) {
9 | return !_.contains(str, badString);
10 | });
11 | }
12 |
13 | export default {
14 | init: init
15 | };
16 |
--------------------------------------------------------------------------------
/core/client/config-dev.js:
--------------------------------------------------------------------------------
1 | function configureApp(App) {
2 | if (!App instanceof Ember.Application) {
3 | return;
4 | }
5 |
6 | App.reopen({
7 | LOG_ACTIVE_GENERATION: true,
8 | LOG_MODULE_RESOLVER: true,
9 | LOG_TRANSITIONS: true,
10 | LOG_TRANSITIONS_INTERNAL: true,
11 | LOG_VIEW_LOOKUPS: true
12 | });
13 | }
14 |
15 | export default configureApp;
16 |
--------------------------------------------------------------------------------
/core/client/models/role.js:
--------------------------------------------------------------------------------
1 | var Role = DS.Model.extend({
2 | uuid: DS.attr('string'),
3 | name: DS.attr('string'),
4 | description: DS.attr('string'),
5 | created_at: DS.attr('moment-date'),
6 | updated_at: DS.attr('moment-date'),
7 |
8 | lowerCaseName: Ember.computed('name', function () {
9 | return this.get('name').toLocaleLowerCase();
10 | })
11 | });
12 |
13 | export default Role;
14 |
--------------------------------------------------------------------------------
/core/server/errors/emailerror.js:
--------------------------------------------------------------------------------
1 | // # Email error
2 | // Custom error class with status code and type prefilled.
3 |
4 | function EmailError(message) {
5 | this.message = message;
6 | this.stack = new Error().stack;
7 | this.code = 500;
8 | this.type = this.name;
9 | }
10 |
11 | EmailError.prototype = Object.create(Error.prototype);
12 | EmailError.prototype.name = 'EmailError';
13 |
14 |
15 | module.exports = EmailError;
--------------------------------------------------------------------------------
/core/server/models/client.js:
--------------------------------------------------------------------------------
1 | var ghostBookshelf = require('./base'),
2 |
3 | Client,
4 | Clients;
5 |
6 |
7 | Client = ghostBookshelf.Model.extend({
8 | tableName: 'clients'
9 | });
10 |
11 | Clients = ghostBookshelf.Collection.extend({
12 | model: Client
13 | });
14 |
15 | module.exports = {
16 | Client: ghostBookshelf.model('Client', Client),
17 | Clients: ghostBookshelf.collection('Clients', Clients)
18 | };
--------------------------------------------------------------------------------
/core/client/templates/modals/leave-editor.hbs:
--------------------------------------------------------------------------------
1 | {{#gh-modal-dialog action="closeModal" showClose=true type="action" style="wide,centered" animation="fade"
2 | title="Are you sure you want to leave this page?" confirm=confirm}}
3 |
4 | Hey there! It looks like you're in the middle of writing something and you haven't saved all of your
5 | content.
6 |
7 | Save before you go!
8 |
9 | {{/gh-modal-dialog}}
10 |
--------------------------------------------------------------------------------
/core/test/functional/frontend/route_test.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Tests archive page routing
3 | */
4 |
5 | /*globals CasperTest, casper, __utils__, url, testPost, falseUser, email */
6 | CasperTest.begin('Redirects page 1 request', 1, function suite(test) {
7 | casper.thenOpen(url + 'page/1/', function then(response) {
8 | test.assertEqual(casper.getCurrentUrl().indexOf('page/'), -1, 'Should be redirected to "/".');
9 | });
10 | }, true);
--------------------------------------------------------------------------------
/core/client/templates/modals/upload.hbs:
--------------------------------------------------------------------------------
1 | {{#gh-upload-modal action="closeModal" close=true type="action" style="wide" model=model imageType=imageType
2 | animation="fade"}}
3 |
7 |
8 | {{/gh-upload-modal}}
9 |
--------------------------------------------------------------------------------
/core/server/errors/notfounderror.js:
--------------------------------------------------------------------------------
1 | // # Not found error
2 | // Custom error class with status code and type prefilled.
3 |
4 | function NotFoundError(message) {
5 | this.message = message;
6 | this.stack = new Error().stack;
7 | this.code = 404;
8 | this.type = this.name;
9 | }
10 |
11 | NotFoundError.prototype = Object.create(Error.prototype);
12 | NotFoundError.prototype.name = 'NotFoundError';
13 |
14 |
15 | module.exports = NotFoundError;
--------------------------------------------------------------------------------
/core/client/components/gh-trim-focus-input.js:
--------------------------------------------------------------------------------
1 | var TrimFocusInput = Ember.TextField.extend({
2 | focus: true,
3 |
4 | setFocus: function () {
5 | if (this.focus) {
6 | this.$().val(this.$().val()).focus();
7 | }
8 | }.on('didInsertElement'),
9 |
10 | focusOut: function () {
11 | var text = this.$().val();
12 |
13 | this.$().val(text.trim());
14 | }
15 | });
16 |
17 | export default TrimFocusInput;
18 |
--------------------------------------------------------------------------------
/core/server/errors/badrequesterror.js:
--------------------------------------------------------------------------------
1 | // # Bad request error
2 | // Custom error class with status code and type prefilled.
3 |
4 | function BadRequestError(message) {
5 | this.message = message;
6 | this.stack = new Error().stack;
7 | this.code = 400;
8 | this.type = this.name;
9 | }
10 |
11 | BadRequestError.prototype = Object.create(Error.prototype);
12 | BadRequestError.prototype.name = 'BadRequestError';
13 |
14 |
15 | module.exports = BadRequestError;
--------------------------------------------------------------------------------
/core/client/components/gh-form.js:
--------------------------------------------------------------------------------
1 | var Form = Ember.View.extend({
2 | tagName: 'form',
3 | attributeBindings: ['enctype'],
4 | reset: function () {
5 | this.$().get(0).reset();
6 | },
7 | didInsertElement: function () {
8 | this.get('controller').on('reset', this, this.reset);
9 | },
10 | willClearRender: function () {
11 | this.get('controller').off('reset', this, this.reset);
12 | }
13 | });
14 |
15 | export default Form;
16 |
--------------------------------------------------------------------------------
/core/client/views/post-settings-menu-view.js:
--------------------------------------------------------------------------------
1 | /* global moment */
2 | import {formatDate} from 'ghost/utils/date-formatting';
3 |
4 | var PostSettingsMenuView = Ember.View.extend({
5 | templateName: 'post-settings-menu',
6 | publishedAtBinding: Ember.Binding.oneWay('controller.publishedAt'),
7 | datePlaceholder: function () {
8 | return formatDate(moment());
9 | }.property('controller.publishedAt')
10 | });
11 |
12 | export default PostSettingsMenuView;
13 |
--------------------------------------------------------------------------------
/core/server/errors/nopermissionerror.js:
--------------------------------------------------------------------------------
1 | // # No Permission Error
2 | // Custom error class with status code and type prefilled.
3 |
4 | function NoPermissionError(message) {
5 | this.message = message;
6 | this.stack = new Error().stack;
7 | this.code = 403;
8 | this.type = this.name;
9 | }
10 |
11 | NoPermissionError.prototype = Object.create(Error.prototype);
12 | NoPermissionError.prototype.name = 'NoPermissionError';
13 |
14 |
15 | module.exports = NoPermissionError;
--------------------------------------------------------------------------------
/core/server/errors/unauthorizederror.js:
--------------------------------------------------------------------------------
1 | // # Unauthorized error
2 | // Custom error class with status code and type prefilled.
3 |
4 | function UnauthorizedError(message) {
5 | this.message = message;
6 | this.stack = new Error().stack;
7 | this.code = 401;
8 | this.type = this.name;
9 | }
10 |
11 | UnauthorizedError.prototype = Object.create(Error.prototype);
12 | UnauthorizedError.prototype.name = 'UnauthorizedError';
13 |
14 |
15 | module.exports = UnauthorizedError;
--------------------------------------------------------------------------------
/core/client/validators/post.js:
--------------------------------------------------------------------------------
1 | var PostValidator = Ember.Object.create({
2 | check: function (model) {
3 | var validationErrors = [],
4 |
5 | title = model.get('title');
6 |
7 | if (validator.empty(title)) {
8 | validationErrors.push({
9 | message: 'You must specify a title for the post.'
10 | });
11 | }
12 |
13 | return validationErrors;
14 | }
15 | });
16 |
17 | export default PostValidator;
18 |
--------------------------------------------------------------------------------
/core/client/helpers/gh-format-timeago.js:
--------------------------------------------------------------------------------
1 | /* global moment */
2 | var formatTimeago = Ember.Handlebars.makeBoundHelper(function (timeago) {
3 | return moment(timeago).fromNow();
4 | // stefanpenner says cool for small number of timeagos.
5 | // For large numbers moment sucks => single Ember.Object based clock better
6 | // https://github.com/manuelmitasch/ghost-admin-ember-demo/commit/fba3ab0a59238290c85d4fa0d7c6ed1be2a8a82e#commitcomment-5396524
7 | });
8 |
9 | export default formatTimeago;
--------------------------------------------------------------------------------
/core/client/validators/forgotten.js:
--------------------------------------------------------------------------------
1 | var ForgotValidator = Ember.Object.create({
2 | check: function (model) {
3 | var data = model.getProperties('email'),
4 | validationErrors = [];
5 |
6 | if (!validator.isEmail(data.email)) {
7 | validationErrors.push({
8 | message: 'Invalid email address'
9 | });
10 | }
11 |
12 | return validationErrors;
13 | }
14 | });
15 |
16 | export default ForgotValidator;
17 |
--------------------------------------------------------------------------------
/core/server/errors/internalservererror.js:
--------------------------------------------------------------------------------
1 | // # Internal Server Error
2 | // Custom error class with status code and type prefilled.
3 |
4 | function InternalServerError(message) {
5 | this.message = message;
6 | this.stack = new Error().stack;
7 | this.code = 500;
8 | this.type = this.name;
9 | }
10 |
11 | InternalServerError.prototype = Object.create(Error.prototype);
12 | InternalServerError.prototype.name = 'InternalServerError';
13 |
14 |
15 | module.exports = InternalServerError;
--------------------------------------------------------------------------------
/core/client/components/gh-role-selector.js:
--------------------------------------------------------------------------------
1 | import GhostSelect from 'ghost/components/gh-select';
2 |
3 | var RolesSelector = GhostSelect.extend({
4 | roles: Ember.computed.alias('options'),
5 | options: Ember.computed(function () {
6 | var rolesPromise = this.store.find('role', { permissions: 'assign' });
7 |
8 | return Ember.ArrayProxy.extend(Ember.PromiseProxyMixin)
9 | .create({promise: rolesPromise});
10 | })
11 | });
12 |
13 | export default RolesSelector;
14 |
--------------------------------------------------------------------------------
/core/client/transforms/moment-date.js:
--------------------------------------------------------------------------------
1 | /* global moment */
2 | var MomentDate = DS.Transform.extend({
3 | deserialize: function (serialized) {
4 | if (serialized) {
5 | return moment(serialized);
6 | }
7 | return serialized;
8 | },
9 | serialize: function (deserialized) {
10 | if (deserialized) {
11 | return moment(deserialized).toDate();
12 | }
13 | return deserialized;
14 | }
15 | });
16 | export default MomentDate;
17 |
--------------------------------------------------------------------------------
/core/client/utils/mobile.js:
--------------------------------------------------------------------------------
1 | var mobileQuery = matchMedia('(max-width: 800px)'),
2 |
3 | responsiveAction = function responsiveAction(event, mediaCondition, cb) {
4 | if (!window.matchMedia(mediaCondition).matches) {
5 | return;
6 | }
7 |
8 | event.preventDefault();
9 | event.stopPropagation();
10 | cb();
11 | };
12 |
13 | export { mobileQuery, responsiveAction };
14 | export default {
15 | mobileQuery: mobileQuery,
16 | responsiveAction: responsiveAction
17 | };
18 |
--------------------------------------------------------------------------------
/core/server/errors/unsupportedmediaerror.js:
--------------------------------------------------------------------------------
1 | // # Unsupported Media Type
2 | // Custom error class with status code and type prefilled.
3 |
4 | function UnsupportedMediaTypeError(message) {
5 | this.message = message;
6 | this.stack = new Error().stack;
7 | this.code = 415;
8 | this.type = this.name;
9 | }
10 |
11 | UnsupportedMediaTypeError.prototype = Object.create(Error.prototype);
12 | UnsupportedMediaTypeError.prototype.name = 'UnsupportedMediaTypeError';
13 |
14 |
15 | module.exports = UnsupportedMediaTypeError;
--------------------------------------------------------------------------------
/core/server/models/accesstoken.js:
--------------------------------------------------------------------------------
1 | var ghostBookshelf = require('./base'),
2 | Basetoken = require('./basetoken'),
3 |
4 | Accesstoken,
5 | Accesstokens;
6 |
7 | Accesstoken = Basetoken.extend({
8 | tableName: 'accesstokens'
9 | });
10 |
11 | Accesstokens = ghostBookshelf.Collection.extend({
12 | model: Accesstoken
13 | });
14 |
15 | module.exports = {
16 | Accesstoken: ghostBookshelf.model('Accesstoken', Accesstoken),
17 | Accesstokens: ghostBookshelf.collection('Accesstokens', Accesstokens)
18 | };
--------------------------------------------------------------------------------
/core/client/components/gh-popover-button.js:
--------------------------------------------------------------------------------
1 | import PopoverMixin from 'ghost/mixins/popover-mixin';
2 |
3 | var PopoverButton = Ember.Component.extend(PopoverMixin, {
4 | tagName: 'button',
5 | /*matches with the popover this button toggles*/
6 | popoverName: null,
7 | /*Notify popover service this popover should be toggled*/
8 | click: function (event) {
9 | this._super(event);
10 | this.get('popover').togglePopover(this.get('popoverName'), this);
11 | }
12 | });
13 |
14 | export default PopoverButton;
--------------------------------------------------------------------------------
/core/server/errors/requesttoolargeerror.js:
--------------------------------------------------------------------------------
1 | // # Request Entity Too Large Error
2 | // Custom error class with status code and type prefilled.
3 |
4 | function RequestEntityTooLargeError(message) {
5 | this.message = message;
6 | this.stack = new Error().stack;
7 | this.code = 413;
8 | this.type = this.name;
9 | }
10 |
11 | RequestEntityTooLargeError.prototype = Object.create(Error.prototype);
12 | RequestEntityTooLargeError.prototype.name = 'RequestEntityTooLargeError';
13 |
14 |
15 | module.exports = RequestEntityTooLargeError;
--------------------------------------------------------------------------------
/core/server/models/refreshtoken.js:
--------------------------------------------------------------------------------
1 | var ghostBookshelf = require('./base'),
2 | Basetoken = require('./basetoken'),
3 |
4 | Refreshtoken,
5 | Refreshtokens;
6 |
7 | Refreshtoken = Basetoken.extend({
8 | tableName: 'refreshtokens'
9 | });
10 |
11 | Refreshtokens = ghostBookshelf.Collection.extend({
12 | model: Refreshtoken
13 | });
14 |
15 | module.exports = {
16 | Refreshtoken: ghostBookshelf.model('Refreshtoken', Refreshtoken),
17 | Refreshtokens: ghostBookshelf.collection('Refreshtokens', Refreshtokens)
18 | };
--------------------------------------------------------------------------------
/core/client/helpers/gh-count-characters.js:
--------------------------------------------------------------------------------
1 | var countCharacters = Ember.Handlebars.makeBoundHelper(function (content) {
2 | var el = document.createElement('span'),
3 | length = content ? content.length : 0;
4 |
5 | el.className = 'word-count';
6 | if (length > 180) {
7 | el.style.color = '#E25440';
8 | } else {
9 | el.style.color = '#9E9D95';
10 | }
11 |
12 | el.innerHTML = 200 - length;
13 |
14 | return new Ember.Handlebars.SafeString(el.outerHTML);
15 | });
16 |
17 | export default countCharacters;
--------------------------------------------------------------------------------
/core/server/models/appField.js:
--------------------------------------------------------------------------------
1 | var ghostBookshelf = require('./base'),
2 | AppField,
3 | AppFields;
4 |
5 | AppField = ghostBookshelf.Model.extend({
6 | tableName: 'app_fields',
7 |
8 | post: function () {
9 | return this.morphOne('Post', 'relatable');
10 | }
11 | });
12 |
13 | AppFields = ghostBookshelf.Collection.extend({
14 | model: AppField
15 | });
16 |
17 | module.exports = {
18 | AppField: ghostBookshelf.model('AppField', AppField),
19 | AppFields: ghostBookshelf.collection('AppFields', AppFields)
20 | };
--------------------------------------------------------------------------------
/core/client/app.js:
--------------------------------------------------------------------------------
1 | import Resolver from 'ember/resolver';
2 | import loadInitializers from 'ember/load-initializers';
3 | import 'ghost/utils/link-view';
4 | import 'ghost/utils/text-field';
5 | import configureApp from 'ghost/config';
6 |
7 | Ember.MODEL_FACTORY_INJECTIONS = true;
8 |
9 | var App = Ember.Application.extend({
10 | modulePrefix: 'ghost',
11 | Resolver: Resolver['default']
12 | });
13 |
14 | // Runtime configuration of Ember.Application
15 | configureApp(App);
16 |
17 | loadInitializers(App, 'ghost');
18 |
19 | export default App;
20 |
--------------------------------------------------------------------------------
/core/client/controllers/application.js:
--------------------------------------------------------------------------------
1 | var ApplicationController = Ember.Controller.extend({
2 | hideNav: Ember.computed.match('currentPath', /(error|signin|signup|setup|forgotten|reset)/),
3 |
4 | topNotificationCount: 0,
5 |
6 | actions: {
7 | toggleMenu: function () {
8 | this.toggleProperty('showMenu');
9 | },
10 |
11 | topNotificationChange: function (count) {
12 | this.set('topNotificationCount', count);
13 | }
14 | }
15 | });
16 |
17 | export default ApplicationController;
18 |
--------------------------------------------------------------------------------
/core/client/initializers/trailing-history.js:
--------------------------------------------------------------------------------
1 | /*global Ember */
2 |
3 | var trailingHistory = Ember.HistoryLocation.extend({
4 | formatURL: function () {
5 | return this._super.apply(this, arguments).replace(/\/?$/, '/');
6 | }
7 | });
8 |
9 | var registerTrailingLocationHistory = {
10 | name: 'registerTrailingLocationHistory',
11 |
12 | initialize: function (container, application) {
13 | application.register('location:trailing-history', trailingHistory);
14 | }
15 | };
16 |
17 | export default registerTrailingLocationHistory;
--------------------------------------------------------------------------------
/core/server/models/appSetting.js:
--------------------------------------------------------------------------------
1 | var ghostBookshelf = require('./base'),
2 | AppSetting,
3 | AppSettings;
4 |
5 | AppSetting = ghostBookshelf.Model.extend({
6 | tableName: 'app_settings',
7 |
8 | app: function () {
9 | return this.belongsTo('App');
10 | }
11 | });
12 |
13 | AppSettings = ghostBookshelf.Collection.extend({
14 | model: AppSetting
15 | });
16 |
17 | module.exports = {
18 | AppSetting: ghostBookshelf.model('AppSetting', AppSetting),
19 | AppSettings: ghostBookshelf.collection('AppSettings', AppSettings)
20 | };
--------------------------------------------------------------------------------
/core/client/views/post-item-view.js:
--------------------------------------------------------------------------------
1 | import itemView from 'ghost/views/item-view';
2 |
3 | var PostItemView = itemView.extend({
4 | classNameBindings: ['isFeatured:featured', 'isPage:page'],
5 |
6 | isFeatured: Ember.computed.alias('controller.model.featured'),
7 |
8 | isPage: Ember.computed.alias('controller.model.page'),
9 |
10 | //Edit post on double click
11 | doubleClick: function () {
12 | this.get('controller').send('openEditor', this.get('controller.model'));
13 | }
14 |
15 | });
16 |
17 | export default PostItemView;
18 |
--------------------------------------------------------------------------------
/core/server/errors/validationerror.js:
--------------------------------------------------------------------------------
1 | // # Validation Error
2 | // Custom error class with status code and type prefilled.
3 |
4 | function ValidationError(message, offendingProperty) {
5 | this.message = message;
6 | this.stack = new Error().stack;
7 | this.code = 422;
8 | if (offendingProperty) {
9 | this.property = offendingProperty;
10 | }
11 | this.type = this.name;
12 | }
13 |
14 | ValidationError.prototype = Object.create(Error.prototype);
15 | ValidationError.prototype.name = 'ValidationError';
16 |
17 |
18 | module.exports = ValidationError;
19 |
--------------------------------------------------------------------------------
/core/client/controllers/error.js:
--------------------------------------------------------------------------------
1 | var ErrorController = Ember.Controller.extend({
2 | code: function () {
3 | return this.get('content.status') > 200 ? this.get('content.status') : 500;
4 | }.property('content.status'),
5 | message: function () {
6 | if (this.get('code') === 404) {
7 | return 'No Ghost Found';
8 | }
9 |
10 | return this.get('content.statusText') !== 'error' ? this.get('content.statusText') : 'Internal Server Error';
11 | }.property('content.statusText'),
12 | stack: false
13 | });
14 |
15 | export default ErrorController;
--------------------------------------------------------------------------------
/core/client/templates/forgotten.hbs:
--------------------------------------------------------------------------------
1 |
10 |
--------------------------------------------------------------------------------
/core/client/validators/setup.js:
--------------------------------------------------------------------------------
1 | import NewUserValidator from 'ghost/validators/new-user';
2 |
3 | var SetupValidator = NewUserValidator.extend({
4 | check: function (model) {
5 | var data = model.getProperties('blogTitle'),
6 | validationErrors = this._super(model);
7 |
8 | if (!validator.isLength(data.blogTitle, 1)) {
9 | validationErrors.push({
10 | message: 'Please enter a blog title.'
11 | });
12 | }
13 |
14 | return validationErrors;
15 | }
16 | }).create();
17 |
18 | export default SetupValidator;
19 |
--------------------------------------------------------------------------------
/core/client/views/settings/content-base.js:
--------------------------------------------------------------------------------
1 | /**
2 | * All settings views other than the index should inherit from this base class.
3 | * It ensures that the correct screen is showing when a mobile user navigates
4 | * to a `settings.someRouteThatIsntIndex` route.
5 | */
6 |
7 | var SettingsContentBaseView = Ember.View.extend({
8 | tagName: 'section',
9 | classNames: ['settings-content', 'fade-in'],
10 | showContent: function () {
11 | this.get('parentView').showSettingsContent();
12 | }.on('didInsertElement')
13 | });
14 |
15 | export default SettingsContentBaseView;
16 |
--------------------------------------------------------------------------------
/core/server/errors/dataimporterror.js:
--------------------------------------------------------------------------------
1 | // # Data import error
2 | // Custom error class with status code and type prefilled.
3 |
4 | function DataImportError(message, offendingProperty, value) {
5 | this.message = message;
6 | this.stack = new Error().stack;
7 | this.code = 500;
8 | this.type = this.name;
9 | this.property = offendingProperty || undefined;
10 | this.value = value || undefined;
11 | }
12 |
13 | DataImportError.prototype = Object.create(Error.prototype);
14 | DataImportError.prototype.name = 'DataImportError';
15 |
16 |
17 | module.exports = DataImportError;
--------------------------------------------------------------------------------
/core/test/utils/fixtures/app/good.js:
--------------------------------------------------------------------------------
1 |
2 | var path = require('path'),
3 | util = require('./goodlib.js'),
4 | nested = require('./nested/goodnested');
5 |
6 | function GoodApp(app) {
7 | this.app = app;
8 | }
9 |
10 | GoodApp.prototype.install = function () {
11 | // Goes through app to do data
12 | this.app.something = 42;
13 | this.app.util = util;
14 | this.app.nested = nested;
15 | this.app.path = path.join(__dirname, 'good.js');
16 |
17 | return true;
18 | };
19 |
20 | GoodApp.prototype.activate = function () {
21 |
22 | };
23 |
24 | module.exports = GoodApp;
--------------------------------------------------------------------------------
/core/client/mixins/nprogress-save.js:
--------------------------------------------------------------------------------
1 | var NProgressSaveMixin = Ember.Mixin.create({
2 | save: function (options) {
3 | if (options && options.disableNProgress) {
4 | return this._super(options);
5 | }
6 |
7 | NProgress.start();
8 | return this._super(options).then(function (value) {
9 | NProgress.done();
10 | return value;
11 | }).catch(function (error) {
12 | NProgress.done();
13 | return Ember.RSVP.reject(error);
14 | });
15 | }
16 | });
17 |
18 | export default NProgressSaveMixin;
--------------------------------------------------------------------------------
/core/client/validators/signin.js:
--------------------------------------------------------------------------------
1 | var SigninValidator = Ember.Object.create({
2 | check: function (model) {
3 | var data = model.getProperties('identification', 'password'),
4 | validationErrors = [];
5 |
6 | if (!validator.isEmail(data.identification)) {
7 | validationErrors.push('Invalid Email');
8 | }
9 |
10 | if (!validator.isLength(data.password || '', 1)) {
11 | validationErrors.push('Please enter a password');
12 | }
13 |
14 | return validationErrors;
15 | }
16 | });
17 |
18 | export default SigninValidator;
19 |
--------------------------------------------------------------------------------
/core/client/initializers/ghost-paths.js:
--------------------------------------------------------------------------------
1 | import ghostPaths from 'ghost/utils/ghost-paths';
2 |
3 | var ghostPathsInitializer = {
4 | name: 'ghost-paths',
5 | after: 'store',
6 |
7 | initialize: function (container, application) {
8 | application.register('ghost:paths', ghostPaths(), { instantiate: false });
9 |
10 | application.inject('route', 'ghostPaths', 'ghost:paths');
11 | application.inject('model', 'ghostPaths', 'ghost:paths');
12 | application.inject('controller', 'ghostPaths', 'ghost:paths');
13 | }
14 | };
15 |
16 | export default ghostPathsInitializer;
17 |
--------------------------------------------------------------------------------
/core/client/utils/titleize.js:
--------------------------------------------------------------------------------
1 | var lowerWords = ['of', 'a', 'the', 'and', 'an', 'or', 'nor', 'but', 'is', 'if',
2 | 'then', 'else', 'when', 'at', 'from', 'by', 'on', 'off', 'for',
3 | 'in', 'out', 'over', 'to', 'into', 'with'];
4 |
5 | function titleize(input) {
6 | var words = input.split(' ').map(function (word, index) {
7 | if (index === 0 || lowerWords.indexOf(word) === -1) {
8 | word = Ember.String.capitalize(word);
9 | }
10 |
11 | return word;
12 | });
13 |
14 | return words.join(' ');
15 | }
16 |
17 | export default titleize;
18 |
--------------------------------------------------------------------------------
/core/server/storage/index.js:
--------------------------------------------------------------------------------
1 | var errors = require('../errors'),
2 | storage;
3 |
4 | function get_storage() {
5 | // TODO: this is where the check for storage apps should go
6 | // Local file system is the default
7 | var storageChoice = 'localfilesystem';
8 |
9 | if (storage) {
10 | return storage;
11 | }
12 |
13 | try {
14 | // TODO: determine if storage has all the necessary methods
15 | storage = require('./' + storageChoice);
16 | } catch (e) {
17 | errors.logError(e);
18 | }
19 | return storage;
20 | }
21 |
22 | module.exports.get_storage = get_storage;
--------------------------------------------------------------------------------
/core/client/controllers/posts/post.js:
--------------------------------------------------------------------------------
1 | var PostController = Ember.ObjectController.extend({
2 | isPublished: Ember.computed.equal('status', 'published'),
3 | classNameBindings: ['featured'],
4 |
5 | actions: {
6 | toggleFeatured: function () {
7 | var options = {disableNProgress: true},
8 | self = this;
9 |
10 | this.toggleProperty('featured');
11 | this.get('model').save(options).catch(function (errors) {
12 | self.notifications.showErrors(errors);
13 | });
14 | }
15 | }
16 | });
17 |
18 | export default PostController;
19 |
--------------------------------------------------------------------------------
/core/client/serializers/application.js:
--------------------------------------------------------------------------------
1 | var ApplicationSerializer = DS.RESTSerializer.extend({
2 | serializeIntoHash: function (hash, type, record, options) {
3 | // Our API expects an id on the posted object
4 | options = options || {};
5 | options.includeId = true;
6 |
7 | // We have a plural root in the API
8 | var root = Ember.String.pluralize(type.typeKey),
9 | data = this.serialize(record, options);
10 |
11 | // Don't ever pass uuid's
12 | delete data.uuid;
13 |
14 | hash[root] = [data];
15 | }
16 | });
17 |
18 | export default ApplicationSerializer;
19 |
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "node": true,
3 | "browser": false,
4 | "nomen": false,
5 | "strict": false,
6 | "sub": true,
7 | "eqeqeq": true,
8 | "laxbreak": true,
9 | "bitwise": true,
10 | "curly": true,
11 | "forin": true,
12 | "immed": true,
13 | "latedef": true,
14 | "newcap": true,
15 | "noarg": true,
16 | "noempty": true,
17 | "nonew": true,
18 | "plusplus": true,
19 | "regexp": true,
20 | "undef": true,
21 | "unused": true,
22 | "trailing": true,
23 | "indent": 4,
24 | "onevar": true,
25 | "white": true
26 | }
27 |
--------------------------------------------------------------------------------
/core/client/views/content-list-content-view.js:
--------------------------------------------------------------------------------
1 | import setScrollClassName from 'ghost/utils/set-scroll-classname';
2 | import PaginationViewMixin from 'ghost/mixins/pagination-view-infinite-scroll';
3 |
4 |
5 | var PostsListView = Ember.View.extend(PaginationViewMixin, {
6 | classNames: ['content-list-content'],
7 |
8 | didInsertElement: function () {
9 | this._super();
10 | var el = this.$();
11 | el.on('scroll', Ember.run.bind(el, setScrollClassName, {
12 | target: el.closest('.content-list'),
13 | offset: 10
14 | }));
15 | }
16 | });
17 |
18 | export default PostsListView;
19 |
--------------------------------------------------------------------------------
/core/client/views/content-preview-content-view.js:
--------------------------------------------------------------------------------
1 | import setScrollClassName from 'ghost/utils/set-scroll-classname';
2 |
3 | var PostContentView = Ember.View.extend({
4 | classNames: ['content-preview-content'],
5 |
6 | didInsertElement: function () {
7 | var el = this.$();
8 | el.on('scroll', Ember.run.bind(el, setScrollClassName, {
9 | target: el.closest('.content-preview'),
10 | offset: 10
11 | }));
12 | },
13 |
14 | willDestroyElement: function () {
15 | var el = this.$();
16 | el.off('scroll');
17 | }
18 | });
19 |
20 | export default PostContentView;
21 |
--------------------------------------------------------------------------------
/core/client/routes/settings/apps.js:
--------------------------------------------------------------------------------
1 | import CurrentUserSettings from 'ghost/mixins/current-user-settings';
2 |
3 | var AppsRoute = Ember.Route.extend(SimpleAuth.AuthenticatedRouteMixin, CurrentUserSettings, {
4 | beforeModel: function () {
5 | if (!this.get('config.apps')) {
6 | return this.transitionTo('settings.general');
7 | }
8 |
9 | return this.currentUser()
10 | .then(this.transitionAuthor())
11 | .then(this.transitionEditor());
12 | },
13 |
14 | model: function () {
15 | return this.store.find('app');
16 | }
17 | });
18 |
19 | export default AppsRoute;
20 |
--------------------------------------------------------------------------------
/core/client/controllers/editor/new.js:
--------------------------------------------------------------------------------
1 | import EditorControllerMixin from 'ghost/mixins/editor-base-controller';
2 |
3 | var EditorNewController = Ember.ObjectController.extend(EditorControllerMixin, {
4 | actions: {
5 | /**
6 | * Redirect to editor after the first save
7 | */
8 | save: function () {
9 | var self = this;
10 | this._super().then(function (model) {
11 | if (model.get('id')) {
12 | self.transitionToRoute('editor.edit', model);
13 | }
14 | });
15 | }
16 | }
17 | });
18 |
19 | export default EditorNewController;
20 |
--------------------------------------------------------------------------------
/core/client/utils/caja-sanitizers.js:
--------------------------------------------------------------------------------
1 | /**
2 | * google-caja uses url() and id() to verify if the values are allowed.
3 | */
4 | var url,
5 | id;
6 |
7 | /**
8 | * Check if URL is allowed
9 | * URLs are allowed if they start with http://, https://, or /.
10 | */
11 | var url = function (url) {
12 | url = url.toString().replace(/['"]+/g, '');
13 | if (/^https?:\/\//.test(url) || /^\//.test(url)) {
14 | return url;
15 | }
16 | };
17 |
18 | /**
19 | * Check if ID is allowed
20 | * All ids are allowed at the moment.
21 | */
22 | var id = function (id) {
23 | return id;
24 | };
25 |
26 | export default {
27 | url: url,
28 | id: id
29 | };
--------------------------------------------------------------------------------
/core/client/controllers/modals/upload.js:
--------------------------------------------------------------------------------
1 |
2 | var UploadController = Ember.Controller.extend({
3 | acceptEncoding: 'image/*',
4 | actions: {
5 | confirmAccept: function () {
6 | var self = this;
7 |
8 | this.get('model').save().then(function (model) {
9 | self.notifications.showSuccess('Saved');
10 | return model;
11 | }).catch(function (err) {
12 | self.notifications.showErrors(err);
13 | });
14 | },
15 |
16 | confirmReject: function () {
17 | return false;
18 | }
19 | }
20 | });
21 |
22 | export default UploadController;
23 |
--------------------------------------------------------------------------------
/core/client/mixins/pagination-route.js:
--------------------------------------------------------------------------------
1 | var defaultPaginationSettings = {
2 | page: 1,
3 | limit: 15
4 | };
5 |
6 | var PaginationRoute = Ember.Mixin.create({
7 |
8 | /**
9 | * Sets up pagination details
10 | * @param {settings}: object that specifies additional pagination details
11 | */
12 | setupPagination: function (settings) {
13 |
14 | settings = settings || {};
15 | settings = _.defaults(settings, defaultPaginationSettings);
16 |
17 | this.set('paginationSettings', settings);
18 | this.controller.set('paginationSettings', settings);
19 | }
20 |
21 | });
22 |
23 | export default PaginationRoute;
24 |
--------------------------------------------------------------------------------
/core/client/routes/signout.js:
--------------------------------------------------------------------------------
1 | import styleBody from 'ghost/mixins/style-body';
2 | import loadingIndicator from 'ghost/mixins/loading-indicator';
3 |
4 | var SignoutRoute = Ember.Route.extend(SimpleAuth.AuthenticatedRouteMixin, styleBody, loadingIndicator, {
5 | classNames: ['ghost-signout'],
6 |
7 | afterModel: function (model, transition) {
8 | this.notifications.clear();
9 | if (Ember.canInvoke(transition, 'send')) {
10 | transition.send('invalidateSession');
11 | transition.abort();
12 | } else {
13 | this.send('invalidateSession');
14 | }
15 | },
16 | });
17 |
18 | export default SignoutRoute;
19 |
--------------------------------------------------------------------------------
/core/client/templates/settings.hbs:
--------------------------------------------------------------------------------
1 |
18 |
19 | {{outlet}}
20 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | !**
2 | .build
3 | .dist
4 | .tmp
5 | docs/**
6 | _site/**
7 | content/images/**
8 | !content/images/README.md
9 | content/themes/**
10 | !content/themes/casper/**
11 | content/apps/**
12 | !content/apps/README.md
13 | content/data/**
14 | !content/data/README.md
15 | node_modules/**
16 | core/test/**
17 | **/*.db*
18 | *.db*
19 | .sass*
20 | .af*
21 | .git*
22 | .groc*
23 | .jshintrc
24 | *.iml
25 | config.js
26 | core/built/scripts/templates.js
27 | core/built/scripts/vendor.js
28 | core/built/scripts/templates.js
29 | core/built/scripts/ghost.js
30 | core/built/**/*.map
31 | core/client/**
32 | !core/client/assets/**
33 | CONTRIBUTING.md
34 | SECURITY.md
35 | .travis.yml
36 | *.html
37 | bower_components/**
--------------------------------------------------------------------------------
/core/client/mixins/loading-indicator.js:
--------------------------------------------------------------------------------
1 | // mixin used for routes to display a loading indicator when there is network activity
2 | var loaderOptions = {
3 | 'showSpinner': false
4 | };
5 | NProgress.configure(loaderOptions);
6 |
7 | var loadingIndicator = Ember.Mixin.create({
8 | actions: {
9 |
10 | loading: function () {
11 | NProgress.start();
12 | this.router.one('didTransition', function () {
13 | NProgress.done();
14 | });
15 | return true;
16 | },
17 |
18 | error: function () {
19 | NProgress.done();
20 | return true;
21 | }
22 | }
23 | });
24 |
25 | export default loadingIndicator;
--------------------------------------------------------------------------------
/core/client/templates/post-tags-input.hbs:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{#each controller.tags}}
4 | {{#view view.tagView tag=this}}
5 | {{view.tag.name}}
6 | {{/view}}
7 | {{/each}}
8 |
9 |
10 | {{view view.tagInputView class="tag-input" id="tags" value=newTagText}}
11 |
18 |
--------------------------------------------------------------------------------
/core/client/mixins/current-user-settings.js:
--------------------------------------------------------------------------------
1 | var CurrentUserSettings = Ember.Mixin.create({
2 | currentUser: function () {
3 | return this.store.find('user', 'me');
4 | },
5 |
6 | transitionAuthor: function () {
7 | var self = this;
8 |
9 | return function (user) {
10 | if (user.get('isAuthor')) {
11 | return self.transitionTo('settings.users.user', user);
12 | }
13 |
14 | return user;
15 | };
16 | },
17 |
18 | transitionEditor: function () {
19 | var self = this;
20 |
21 | return function (user) {
22 | if (user.get('isEditor')) {
23 | return self.transitionTo('settings.users');
24 | }
25 |
26 | return user;
27 | };
28 | }
29 | });
30 |
31 | export default CurrentUserSettings;
--------------------------------------------------------------------------------
/core/client/models/setting.js:
--------------------------------------------------------------------------------
1 | import ValidationEngine from 'ghost/mixins/validation-engine';
2 | import NProgressSaveMixin from 'ghost/mixins/nprogress-save';
3 |
4 | var Setting = DS.Model.extend(NProgressSaveMixin, ValidationEngine, {
5 | validationType: 'setting',
6 |
7 | title: DS.attr('string'),
8 | description: DS.attr('string'),
9 | email: DS.attr('string'),
10 | logo: DS.attr('string'),
11 | cover: DS.attr('string'),
12 | defaultLang: DS.attr('string'),
13 | postsPerPage: DS.attr('number'),
14 | forceI18n: DS.attr('boolean'),
15 | permalinks: DS.attr('string'),
16 | activeTheme: DS.attr('string'),
17 | availableThemes: DS.attr()
18 | });
19 |
20 | export default Setting;
21 |
--------------------------------------------------------------------------------
/core/client/routes/reset.js:
--------------------------------------------------------------------------------
1 | import styleBody from 'ghost/mixins/style-body';
2 | import loadingIndicator from 'ghost/mixins/loading-indicator';
3 |
4 | var ResetRoute = Ember.Route.extend(styleBody, loadingIndicator, {
5 | classNames: ['ghost-reset'],
6 | beforeModel: function () {
7 | if (this.get('session').isAuthenticated) {
8 | this.notifications.showWarn('You can\'t reset your password while you\'re signed in.', { delayed: true });
9 | this.transitionTo(SimpleAuth.Configuration.routeAfterAuthentication);
10 | }
11 | },
12 | setupController: function (controller, params) {
13 | controller.token = params.token;
14 | }
15 | });
16 |
17 | export default ResetRoute;
18 |
--------------------------------------------------------------------------------
/core/client/routes/settings/general.js:
--------------------------------------------------------------------------------
1 | import loadingIndicator from 'ghost/mixins/loading-indicator';
2 | import CurrentUserSettings from 'ghost/mixins/current-user-settings';
3 |
4 | var SettingsGeneralRoute = Ember.Route.extend(SimpleAuth.AuthenticatedRouteMixin, loadingIndicator, CurrentUserSettings, {
5 | beforeModel: function () {
6 | return this.currentUser()
7 | .then(this.transitionAuthor())
8 | .then(this.transitionEditor());
9 | },
10 |
11 | model: function () {
12 | return this.store.find('setting', { type: 'blog,theme' }).then(function (records) {
13 | return records.get('firstObject');
14 | });
15 | }
16 | });
17 |
18 | export default SettingsGeneralRoute;
19 |
--------------------------------------------------------------------------------
/core/client/initializers/ghost-config.js:
--------------------------------------------------------------------------------
1 | var ConfigInitializer = {
2 | name: 'config',
3 |
4 | initialize: function (container, application) {
5 | var apps = $('body').data('apps'),
6 | fileStorage = $('body').data('filestorage'),
7 | blogUrl = $('body').data('blogurl');
8 |
9 | application.register(
10 | 'ghost:config', {apps: apps, fileStorage: fileStorage, blogUrl: blogUrl}, {instantiate: false}
11 | );
12 |
13 | application.inject('route', 'config', 'ghost:config');
14 | application.inject('controller', 'config', 'ghost:config');
15 | application.inject('component', 'config', 'ghost:config');
16 | }
17 | };
18 |
19 | export default ConfigInitializer;
20 |
--------------------------------------------------------------------------------
/core/client/initializers/notifications.js:
--------------------------------------------------------------------------------
1 | import Notifications from 'ghost/utils/notifications';
2 |
3 | var injectNotificationsInitializer = {
4 | name: 'injectNotifications',
5 | before: 'authentication',
6 |
7 | initialize: function (container, application) {
8 | application.register('notifications:main', Notifications);
9 |
10 | application.inject('controller', 'notifications', 'notifications:main');
11 | application.inject('component', 'notifications', 'notifications:main');
12 | application.inject('router', 'notifications', 'notifications:main');
13 | application.inject('route', 'notifications', 'notifications:main');
14 | }
15 | };
16 |
17 | export default injectNotificationsInitializer;
18 |
--------------------------------------------------------------------------------
/core/client/templates/reset.hbs:
--------------------------------------------------------------------------------
1 |
12 |
--------------------------------------------------------------------------------
/core/server/api.js:
--------------------------------------------------------------------------------
1 | /*
2 | This file is a shim to accomodate simple file system merge upgrade from 0.3.3 to 0.4.
3 | During a file system merge upgrade from 0.3.3 to 0.4, the old version of this file will
4 | persist unless this shim is in place. Problems arise when the stale 0.3.3 version of
5 | this file exists as well as the new directory of files bearing the same name in 0.4.
6 | Node's require defaults to loading the 0.3.3 version causing errors. This file replaces
7 | the old 0.3.3 version. This allows all dependent modules to continue requiring their
8 | dependencies the way they always have. See issue 1873 for more information.
9 |
10 | https://github.com/TryGhost/Ghost/issues/1873
11 | */
12 | var api = require('./api/index.js');
13 |
14 | module.exports = api;
15 |
--------------------------------------------------------------------------------
/core/server/models/permission.js:
--------------------------------------------------------------------------------
1 | var ghostBookshelf = require('./base'),
2 |
3 | Permission,
4 | Permissions;
5 |
6 | Permission = ghostBookshelf.Model.extend({
7 |
8 | tableName: 'permissions',
9 |
10 | roles: function () {
11 | return this.belongsToMany('Role');
12 | },
13 |
14 | users: function () {
15 | return this.belongsToMany('User');
16 | },
17 |
18 | apps: function () {
19 | return this.belongsToMany('App');
20 | }
21 | });
22 |
23 | Permissions = ghostBookshelf.Collection.extend({
24 | model: Permission
25 | });
26 |
27 | module.exports = {
28 | Permission: ghostBookshelf.model('Permission', Permission),
29 | Permissions: ghostBookshelf.collection('Permissions', Permissions)
30 | };
31 |
--------------------------------------------------------------------------------
/core/server.js:
--------------------------------------------------------------------------------
1 | /*
2 | This file is a shim to accomodate simple file system merge upgrade from 0.3.3 to 0.4.
3 | During a file system merge upgrade from 0.3.3 to 0.4, the old version of this file will
4 | persist unless this shim is in place. Problems arise when the stale 0.3.3 version of
5 | this file exists as well as the new directory of files bearing the same name in 0.4.
6 | Node's require defaults to loading the 0.3.3 version causing errors. This file replaces
7 | the old 0.3.3 version. This allows all dependent modules to continue requiring their
8 | dependencies the way they always have. See issue 1873 for more information.
9 |
10 | https://github.com/TryGhost/Ghost/issues/1873
11 | */
12 | var server = require('./server/index.js');
13 |
14 | module.exports = server;
15 |
--------------------------------------------------------------------------------
/core/client/validators/reset.js:
--------------------------------------------------------------------------------
1 | var ResetValidator = Ember.Object.create({
2 | check: function (model) {
3 |
4 | var data = model.getProperties('passwords'),
5 | p1 = data.passwords.newPassword,
6 | p2 = data.passwords.ne2Password,
7 | validationErrors = [];
8 |
9 | if (!validator.equals(p1, p2)) {
10 | validationErrors.push({
11 | message: 'The two new passwords don\'t match.'
12 | });
13 | }
14 |
15 | if (!validator.isLength(p1, 8)) {
16 | validationErrors.push({
17 | message: 'The password is not long enough.'
18 | });
19 | }
20 | return validationErrors;
21 | }
22 | });
23 |
24 | export default ResetValidator;
25 |
--------------------------------------------------------------------------------
/core/server/middleware.js:
--------------------------------------------------------------------------------
1 | /*
2 | This file is a shim to accomodate simple file system merge upgrade from 0.3.3 to 0.4.
3 | During a file system merge upgrade from 0.3.3 to 0.4, the old version of this file will
4 | persist unless this shim is in place. Problems arise when the stale 0.3.3 version of
5 | this file exists as well as the new directory of files bearing the same name in 0.4.
6 | Node's require defaults to loading the 0.3.3 version causing errors. This file replaces
7 | the old 0.3.3 version. This allows all dependent modules to continue requiring their
8 | dependencies the way they always have. See issue 1873 for more information.
9 |
10 | https://github.com/TryGhost/Ghost/issues/1873
11 | */
12 | var middleware = require('./middleware/index.js');
13 |
14 | module.exports = middleware;
15 |
--------------------------------------------------------------------------------
/core/client/components/gh-file-upload.js:
--------------------------------------------------------------------------------
1 | var FileUpload = Ember.Component.extend({
2 | _file: null,
3 |
4 | uploadButtonText: 'Text',
5 |
6 | uploadButtonDisabled: true,
7 |
8 | change: function (event) {
9 | this.set('uploadButtonDisabled', false);
10 | this.sendAction('onAdd');
11 | this._file = event.target.files[0];
12 | },
13 |
14 | onUpload: 'onUpload',
15 |
16 | actions: {
17 | upload: function () {
18 | if (!this.uploadButtonDisabled && this._file) {
19 | this.sendAction('onUpload', this._file);
20 | }
21 |
22 | // Prevent double post by disabling the button.
23 | this.set('uploadButtonDisabled', true);
24 | }
25 | }
26 | });
27 |
28 | export default FileUpload;
29 |
--------------------------------------------------------------------------------
/core/client/templates/-publish-bar.hbs:
--------------------------------------------------------------------------------
1 |
20 |
--------------------------------------------------------------------------------
/core/client/utils/set-scroll-classname.js:
--------------------------------------------------------------------------------
1 | // ## scrollShadow
2 | // This adds a 'scroll' class to the targeted element when the element is scrolled
3 | // `this` is expected to be a jQuery-wrapped element
4 | // **target:** The element in which the class is applied. Defaults to scrolled element.
5 | // **class-name:** The class which is applied.
6 | // **offset:** How far the user has to scroll before the class is applied.
7 | var setScrollClassName = function (options) {
8 | var $target = options.target || this,
9 | offset = options.offset,
10 | className = options.className || 'scrolling';
11 |
12 | if (this.scrollTop() > offset) {
13 | $target.addClass(className);
14 | } else {
15 | $target.removeClass(className);
16 | }
17 | };
18 |
19 | export default setScrollClassName;
20 |
--------------------------------------------------------------------------------
/core/client/utils/bound-one-way.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Defines a property similarly to `Ember.computed.oneway`,
3 | * save that while a `oneway` loses its binding upon being set,
4 | * the `BoundOneWay` will continue to listen for upstream changes.
5 | *
6 | * This is an ideal tool for working with values inside of {{input}}
7 | * elements.
8 | * @param transform: a function to transform the **upstream** value.
9 | */
10 | var BoundOneWay = function (upstream, transform) {
11 | if (typeof transform !== 'function') {
12 | //default to the identity function
13 | transform = function (value) { return value; };
14 | }
15 | return function (key, value) {
16 | return arguments.length > 1 ? value : transform(this.get(upstream));
17 | }.property(upstream);
18 | };
19 |
20 | export default BoundOneWay;
21 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "0.10"
4 | sudo: false
5 | cache:
6 | directories:
7 | - node_modules
8 | - bower_components
9 | addons:
10 | postgresql: "9.3"
11 | env:
12 | - DB=sqlite3 NODE_ENV=testing
13 | - DB=mysql NODE_ENV=testing-mysql
14 | - DB=pg NODE_ENV=testing-pg
15 | matrix:
16 | allow_failures:
17 | - env: DB=pg NODE_ENV=testing-pg
18 | before_install:
19 | - git clone git://github.com/n1k0/casperjs.git ~/casperjs
20 | - cd ~/casperjs
21 | - git checkout tags/1.1-beta3
22 | - export PATH=$PATH:`pwd`/bin
23 | - cd -
24 | - if [ $DB == "mysql" ]; then mysql -e 'create database ghost_testing'; fi
25 | - if [ $DB == "pg" ]; then npm install pg; psql -c 'create database ghost_testing;' -U postgres; fi
26 | before_script:
27 | - phantomjs --version
28 | - casperjs --version
29 |
--------------------------------------------------------------------------------
/core/test/unit/server_spec.js:
--------------------------------------------------------------------------------
1 | /*globals describe, it*/
2 | /*jshint expr:true*/
3 | var should = require('should'),
4 | request = require('request'),
5 | config = require('../../../config');
6 |
7 | describe('Server', function () {
8 | var port = config.testing.server.port,
9 | host = config.testing.server.host,
10 | url = 'http://' + host + ':' + port;
11 |
12 |
13 | it('should not start a connect server when required', function (done) {
14 | request(url, function (error, response, body) {
15 | should(response).equal(undefined);
16 | should(body).equal(undefined);
17 | should(error).not.equal(undefined);
18 | should(error.code).equal('ECONNREFUSED');
19 | done();
20 | });
21 | });
22 |
23 | });
24 |
25 |
--------------------------------------------------------------------------------
/core/client/adapters/setting.js:
--------------------------------------------------------------------------------
1 | import ApplicationAdapter from 'ghost/adapters/application';
2 |
3 | var SettingAdapter = ApplicationAdapter.extend({
4 | updateRecord: function (store, type, record) {
5 | var data = {},
6 | serializer = store.serializerFor(type.typeKey);
7 |
8 | // remove the fake id that we added onto the model.
9 | delete record.id;
10 |
11 | // use the SettingSerializer to transform the model back into
12 | // an array of settings objects like the API expects
13 | serializer.serializeIntoHash(data, type, record);
14 |
15 | // use the ApplicationAdapter's buildURL method but do not
16 | // pass in an id.
17 | return this.ajax(this.buildURL(type.typeKey), 'PUT', { data: data });
18 | }
19 | });
20 |
21 | export default SettingAdapter;
22 |
--------------------------------------------------------------------------------
/core/client/routes/debug.js:
--------------------------------------------------------------------------------
1 | import styleBody from 'ghost/mixins/style-body';
2 | import loadingIndicator from 'ghost/mixins/loading-indicator';
3 |
4 | var DebugRoute = Ember.Route.extend(SimpleAuth.AuthenticatedRouteMixin, styleBody, loadingIndicator, {
5 | classNames: ['settings'],
6 |
7 | beforeModel: function () {
8 | var self = this;
9 | this.store.find('user', 'me').then(function (user) {
10 | if (user.get('isAuthor') || user.get('isEditor')) {
11 | self.transitionTo('posts');
12 | }
13 | });
14 | },
15 |
16 | model: function () {
17 | return this.store.find('setting', { type: 'blog,theme' }).then(function (records) {
18 | return records.get('firstObject');
19 | });
20 | }
21 |
22 | });
23 |
24 | export default DebugRoute;
25 |
--------------------------------------------------------------------------------
/core/client/helpers/gh-format-html.js:
--------------------------------------------------------------------------------
1 | /* global Handlebars, html_sanitize*/
2 | import cajaSanitizers from 'ghost/utils/caja-sanitizers';
3 |
4 | var formatHTML = Ember.Handlebars.makeBoundHelper(function (html) {
5 | var escapedhtml = html || '';
6 |
7 | // replace script and iFrame
8 | escapedhtml = escapedhtml.replace(/