├── core
├── client
│ ├── tests
│ │ ├── unit
│ │ │ ├── .gitkeep
│ │ │ ├── models
│ │ │ │ ├── tag_test.js
│ │ │ │ ├── setting_test.js
│ │ │ │ └── role_test.js
│ │ │ └── components
│ │ │ │ ├── gh-trim-focus-input_test.js
│ │ │ │ └── gh-url-preview_test.js
│ │ ├── test-helper.js
│ │ ├── helpers
│ │ │ ├── resolver.js
│ │ │ └── start-app.js
│ │ ├── index.html
│ │ └── .jshintrc
│ ├── app
│ │ ├── templates
│ │ │ ├── components
│ │ │ │ ├── gh-url-preview.hbs
│ │ │ │ ├── gh-blog-url.hbs
│ │ │ │ ├── gh-ed-preview.hbs
│ │ │ │ ├── gh-activating-list-item.hbs
│ │ │ │ ├── gh-notifications.hbs
│ │ │ │ ├── gh-role-selector.hbs
│ │ │ │ ├── gh-notification.hbs
│ │ │ │ ├── gh-file-upload.hbs
│ │ │ │ ├── gh-uploader.hbs
│ │ │ │ ├── gh-navitem.hbs
│ │ │ │ └── gh-modal-dialog.hbs
│ │ │ ├── modals
│ │ │ │ ├── delete-post.hbs
│ │ │ │ ├── delete-all.hbs
│ │ │ │ ├── transfer-owner.hbs
│ │ │ │ ├── copy-html.hbs
│ │ │ │ ├── leave-editor.hbs
│ │ │ │ ├── upload.hbs
│ │ │ │ ├── delete-tag.hbs
│ │ │ │ ├── delete-user.hbs
│ │ │ │ ├── signin.hbs
│ │ │ │ └── invite-new-user.hbs
│ │ │ ├── -import-errors.hbs
│ │ │ ├── settings
│ │ │ │ ├── users.hbs
│ │ │ │ ├── navigation.hbs
│ │ │ │ ├── tags.hbs
│ │ │ │ ├── apps.hbs
│ │ │ │ └── code-injection.hbs
│ │ │ ├── posts
│ │ │ │ ├── index.hbs
│ │ │ │ └── post.hbs
│ │ │ ├── -user-actions-menu.hbs
│ │ │ ├── application.hbs
│ │ │ ├── -publish-bar.hbs
│ │ │ ├── forgotten.hbs
│ │ │ ├── reset.hbs
│ │ │ ├── post-tags-input.hbs
│ │ │ ├── signin.hbs
│ │ │ ├── editor-save-button.hbs
│ │ │ └── error.hbs
│ │ ├── utils
│ │ │ ├── mobile.js
│ │ │ ├── text-field.js
│ │ │ ├── ctrl-or-cmd.js
│ │ │ ├── isFinite.js
│ │ │ ├── random-password.js
│ │ │ ├── isNumber.js
│ │ │ ├── bind.js
│ │ │ ├── validator-extensions.js
│ │ │ ├── link-view.js
│ │ │ ├── titleize.js
│ │ │ ├── caja-sanitizers.js
│ │ │ ├── dropdown-service.js
│ │ │ ├── set-scroll-classname.js
│ │ │ ├── bound-one-way.js
│ │ │ ├── word-count.js
│ │ │ └── config-parser.js
│ │ ├── validators
│ │ │ ├── signup.js
│ │ │ ├── forgotten.js
│ │ │ ├── setup.js
│ │ │ ├── signin.js
│ │ │ ├── reset.js
│ │ │ ├── new-user.js
│ │ │ ├── post.js
│ │ │ └── tag-settings.js
│ │ ├── components
│ │ │ ├── gh-blog-url.js
│ │ │ ├── gh-input.js
│ │ │ ├── gh-textarea.js
│ │ │ ├── gh-popover.js
│ │ │ ├── gh-activating-list-item.js
│ │ │ ├── gh-form.js
│ │ │ ├── gh-dropdown-button.js
│ │ │ ├── gh-popover-button.js
│ │ │ ├── gh-file-upload.js
│ │ │ ├── gh-trim-focus-input.js
│ │ │ ├── gh-role-selector.js
│ │ │ ├── gh-navitem.js
│ │ │ ├── gh-notifications.js
│ │ │ ├── gh-url-preview.js
│ │ │ ├── gh-tab-pane.js
│ │ │ ├── gh-tab.js
│ │ │ ├── gh-uploader.js
│ │ │ └── gh-cm-editor.js
│ │ ├── views
│ │ │ ├── settings
│ │ │ │ ├── apps.js
│ │ │ │ ├── labs.js
│ │ │ │ ├── about.js
│ │ │ │ ├── users.js
│ │ │ │ ├── general.js
│ │ │ │ ├── index.js
│ │ │ │ ├── pass-protect.js
│ │ │ │ ├── code-injection.js
│ │ │ │ ├── tags.js
│ │ │ │ ├── users
│ │ │ │ │ ├── users-list-view.js
│ │ │ │ │ └── user.js
│ │ │ │ ├── content-base.js
│ │ │ │ ├── tags
│ │ │ │ │ └── settings-menu.js
│ │ │ │ └── navigation.js
│ │ │ ├── posts
│ │ │ │ ├── post.js
│ │ │ │ └── index.js
│ │ │ ├── editor
│ │ │ │ ├── edit.js
│ │ │ │ └── new.js
│ │ │ ├── mobile
│ │ │ │ ├── index-view.js
│ │ │ │ ├── content-view.js
│ │ │ │ └── parent-view.js
│ │ │ ├── posts.js
│ │ │ ├── paginated-scroll-box.js
│ │ │ ├── content-preview-content-view.js
│ │ │ ├── settings.js
│ │ │ └── editor-save-button.js
│ │ ├── routes
│ │ │ ├── settings
│ │ │ │ ├── users.js
│ │ │ │ ├── apps.js
│ │ │ │ ├── labs.js
│ │ │ │ ├── code-injection.js
│ │ │ │ ├── general.js
│ │ │ │ ├── index.js
│ │ │ │ ├── navigation.js
│ │ │ │ └── about.js
│ │ │ ├── authenticated.js
│ │ │ ├── content.js
│ │ │ ├── debug.js
│ │ │ ├── editor
│ │ │ │ ├── index.js
│ │ │ │ └── new.js
│ │ │ ├── error404.js
│ │ │ ├── forgotten.js
│ │ │ ├── settings.js
│ │ │ ├── signout.js
│ │ │ ├── reset.js
│ │ │ ├── mobile-index-route.js
│ │ │ ├── signin.js
│ │ │ └── setup.js
│ │ ├── adapters
│ │ │ ├── application.js
│ │ │ ├── user.js
│ │ │ ├── setting.js
│ │ │ └── base.js
│ │ ├── controllers
│ │ │ ├── modals
│ │ │ │ ├── copy-html.js
│ │ │ │ ├── upload.js
│ │ │ │ ├── delete-all.js
│ │ │ │ ├── delete-tag.js
│ │ │ │ └── delete-post.js
│ │ │ ├── editor
│ │ │ │ ├── edit.js
│ │ │ │ └── new.js
│ │ │ ├── settings
│ │ │ │ ├── about.js
│ │ │ │ ├── code-injection.js
│ │ │ │ └── users
│ │ │ │ │ └── index.js
│ │ │ ├── error.js
│ │ │ ├── feature.js
│ │ │ ├── application.js
│ │ │ └── posts
│ │ │ │ └── post.js
│ │ ├── initializers
│ │ │ ├── store-injector.js
│ │ │ ├── ghost-paths.js
│ │ │ ├── ghost-config.js
│ │ │ ├── trailing-history.js
│ │ │ ├── notifications.js
│ │ │ └── dropdown.js
│ │ ├── models
│ │ │ ├── notification.js
│ │ │ ├── role.js
│ │ │ ├── tag.js
│ │ │ ├── slug-generator.js
│ │ │ └── setting.js
│ │ ├── _config.yml
│ │ ├── mixins
│ │ │ ├── dropdown-mixin.js
│ │ │ ├── nprogress-save.js
│ │ │ ├── current-user-settings.js
│ │ │ ├── loading-indicator.js
│ │ │ ├── text-input.js
│ │ │ ├── settings-menu-controller.js
│ │ │ ├── pagination-route.js
│ │ │ ├── style-body.js
│ │ │ └── body-event-listener.js
│ │ ├── app.js
│ │ ├── styles
│ │ │ ├── components
│ │ │ │ └── url-preview.scss
│ │ │ ├── vendor
│ │ │ │ └── nanoscroller.scss
│ │ │ └── modules
│ │ │ │ └── variables.scss
│ │ ├── helpers
│ │ │ ├── gh-count-words.js
│ │ │ ├── gh-format-timeago.js
│ │ │ ├── gh-count-characters.js
│ │ │ ├── gh-count-down-characters.js
│ │ │ ├── gh-format-html.js
│ │ │ └── gh-format-markdown.js
│ │ └── serializers
│ │ │ ├── application.js
│ │ │ ├── tag.js
│ │ │ ├── user.js
│ │ │ └── setting.js
│ ├── lib
│ │ ├── .jshintrc
│ │ └── asset-delivery
│ │ │ ├── package.json
│ │ │ └── index.js
│ ├── public
│ │ └── 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
│ │ │ └── fonts
│ │ │ └── icons.woff
│ ├── testem.json
│ ├── .ember-cli
│ ├── .gitignore
│ ├── .jshintrc
│ ├── bower.json
│ └── package.json
├── test
│ ├── utils
│ │ ├── fixtures
│ │ │ ├── test.hbs
│ │ │ ├── import
│ │ │ │ ├── zips
│ │ │ │ │ ├── zip-image-dir
│ │ │ │ │ │ └── images
│ │ │ │ │ │ │ └── image.jpg
│ │ │ │ │ ├── zip-without-base-dir
│ │ │ │ │ │ └── test.json
│ │ │ │ │ ├── zip-with-base-dir
│ │ │ │ │ │ └── basedir
│ │ │ │ │ │ │ └── test.json
│ │ │ │ │ ├── zip-old-roon-export
│ │ │ │ │ │ └── Roon-Export
│ │ │ │ │ │ │ └── published
│ │ │ │ │ │ │ └── test.md
│ │ │ │ │ └── zip-with-double-base-dir
│ │ │ │ │ │ └── basedir
│ │ │ │ │ │ └── basedir
│ │ │ │ │ │ └── test.json
│ │ │ │ ├── deleted-2014-12-19-test-1.md
│ │ │ │ ├── draft-2014-12-19-test-1.md
│ │ │ │ ├── draft-2014-12-19-test-2.md
│ │ │ │ ├── published-2014-12-19-test-1.md
│ │ │ │ └── draft-2014-12-19-test-3.md
│ │ │ ├── theme
│ │ │ │ └── partials
│ │ │ │ │ └── test.hbs
│ │ │ ├── example.js
│ │ │ ├── app
│ │ │ │ ├── badlib.js
│ │ │ │ ├── goodlib.js
│ │ │ │ ├── nested
│ │ │ │ │ └── goodnested.js
│ │ │ │ ├── badoutside.js
│ │ │ │ ├── badtop.js
│ │ │ │ ├── badinstall.js
│ │ │ │ ├── badrequire.js
│ │ │ │ └── good.js
│ │ │ └── export
│ │ │ │ ├── export-003-minimal.json
│ │ │ │ ├── export-003-api-wrapper.json
│ │ │ │ └── export-003-api-wrapper-bad.json
│ │ └── jscs-rules
│ │ │ └── disallow-object-controller.js
│ ├── unit
│ │ ├── server_helpers
│ │ │ ├── test_tpl
│ │ │ │ ├── pagination.hbs
│ │ │ │ └── navigation.hbs
│ │ │ ├── encode_spec.js
│ │ │ ├── input_password_spec.js
│ │ │ ├── utils.js
│ │ │ └── title_spec.js
│ │ ├── server_spec.js
│ │ └── server_helpers_index_spec.js
│ ├── .jshintrc
│ └── functional
│ │ └── client
│ │ └── signout_test.js
├── shared
│ ├── private-robots.txt
│ ├── robots.txt
│ ├── img
│ │ ├── user-cover.png
│ │ └── user-image.png
│ └── favicon.ico
├── server
│ ├── data
│ │ ├── xml
│ │ │ └── sitemap
│ │ │ │ ├── index.js
│ │ │ │ ├── utils.js
│ │ │ │ └── handler.js
│ │ ├── utils
│ │ │ └── clients
│ │ │ │ ├── index.js
│ │ │ │ └── sqlite3.js
│ │ └── importer
│ │ │ └── importers
│ │ │ └── data.js
│ ├── helpers
│ │ ├── tpl
│ │ │ ├── navigation.hbs
│ │ │ └── pagination.hbs
│ │ ├── encode.js
│ │ ├── title.js
│ │ ├── utils.js
│ │ ├── image.js
│ │ ├── input_password.js
│ │ ├── is.js
│ │ ├── url.js
│ │ ├── date.js
│ │ ├── ghost_foot.js
│ │ ├── post_class.js
│ │ └── plural.js
│ ├── events
│ │ └── index.js
│ ├── routes
│ │ ├── index.js
│ │ └── admin.js
│ ├── utils
│ │ ├── sequence.js
│ │ └── pipeline.js
│ ├── models
│ │ ├── client.js
│ │ ├── accesstoken.js
│ │ ├── refreshtoken.js
│ │ ├── app-field.js
│ │ ├── app-setting.js
│ │ └── permission.js
│ ├── errors
│ │ ├── email-error.js
│ │ ├── not-found-error.js
│ │ ├── bad-request-error.js
│ │ ├── unauthorized-error.js
│ │ ├── no-permission-error.js
│ │ ├── internal-server-error.js
│ │ ├── unsupported-media-type-error.js
│ │ ├── request-too-large-error.js
│ │ ├── validation-error.js
│ │ └── data-import-error.js
│ ├── storage
│ │ ├── index.js
│ │ └── base.js
│ └── views
│ │ └── error.hbs
└── index.js
├── .bowerrc
├── content
├── apps
│ └── README.md
├── data
│ └── README.md
└── images
│ └── README.md
├── .gitmodules
├── SECURITY.md
├── .editorconfig
├── .jshintrc
├── .npmignore
├── .travis.yml
├── index.js
├── LICENSE
└── .gitignore
/core/client/tests/unit/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/core/test/utils/fixtures/test.hbs:
--------------------------------------------------------------------------------
1 |
HelloWorld
--------------------------------------------------------------------------------
/core/shared/private-robots.txt:
--------------------------------------------------------------------------------
1 | User-agent: *
2 | Disallow: /
--------------------------------------------------------------------------------
/core/client/app/templates/components/gh-url-preview.hbs:
--------------------------------------------------------------------------------
1 | {{url}}
2 |
--------------------------------------------------------------------------------
/core/test/utils/fixtures/import/zips/zip-image-dir/images/image.jpg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/core/test/utils/fixtures/import/zips/zip-without-base-dir/test.json:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/core/client/app/templates/components/gh-blog-url.hbs:
--------------------------------------------------------------------------------
1 | {{{config.blogUrl}}}
--------------------------------------------------------------------------------
/core/test/utils/fixtures/import/zips/zip-with-base-dir/basedir/test.json:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/core/test/utils/fixtures/import/deleted-2014-12-19-test-1.md:
--------------------------------------------------------------------------------
1 | You're live! Nice.
--------------------------------------------------------------------------------
/core/test/utils/fixtures/import/draft-2014-12-19-test-1.md:
--------------------------------------------------------------------------------
1 | You're live! Nice.
--------------------------------------------------------------------------------
/core/test/utils/fixtures/theme/partials/test.hbs:
--------------------------------------------------------------------------------
1 | HelloWorld Themed
--------------------------------------------------------------------------------
/core/client/lib/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "node": true,
3 | "browser": false
4 | }
5 |
--------------------------------------------------------------------------------
/core/test/utils/fixtures/import/zips/zip-old-roon-export/Roon-Export/published/test.md:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/core/test/utils/fixtures/import/zips/zip-with-double-base-dir/basedir/basedir/test.json:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.bowerrc:
--------------------------------------------------------------------------------
1 | {
2 | "cwd": "core/client/",
3 | "directory": "bower_components"
4 | }
5 |
--------------------------------------------------------------------------------
/content/apps/README.md:
--------------------------------------------------------------------------------
1 | # Content / Apps
2 |
3 | Coming soon, Ghost apps will appear here.
--------------------------------------------------------------------------------
/core/client/app/templates/components/gh-ed-preview.hbs:
--------------------------------------------------------------------------------
1 | {{gh-format-markdown markdown}}
2 |
--------------------------------------------------------------------------------
/core/test/utils/fixtures/example.js:
--------------------------------------------------------------------------------
1 |
2 | module.exports = {
3 | answer: 42
4 | };
5 |
--------------------------------------------------------------------------------
/core/shared/robots.txt:
--------------------------------------------------------------------------------
1 | User-agent: *
2 | Sitemap: {{blog-url}}/sitemap.xml
3 | Disallow: /ghost/
4 |
--------------------------------------------------------------------------------
/core/shared/img/user-cover.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/trheyi/Ghost/HEAD/core/shared/img/user-cover.png
--------------------------------------------------------------------------------
/core/shared/img/user-image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/trheyi/Ghost/HEAD/core/shared/img/user-image.png
--------------------------------------------------------------------------------
/core/test/utils/fixtures/import/draft-2014-12-19-test-2.md:
--------------------------------------------------------------------------------
1 | # Welcome to Ghost
2 |
3 | You're live! Nice.
--------------------------------------------------------------------------------
/core/test/utils/fixtures/import/published-2014-12-19-test-1.md:
--------------------------------------------------------------------------------
1 | #Welcome to Ghost
2 |
3 | You're live! Nice.
--------------------------------------------------------------------------------
/content/data/README.md:
--------------------------------------------------------------------------------
1 | # Content / Data
2 |
3 | If using the standard file storage, Ghost will save default db here.
--------------------------------------------------------------------------------
/core/client/app/utils/mobile.js:
--------------------------------------------------------------------------------
1 | var mobileQuery = matchMedia('(max-width: 900px)');
2 |
3 | export default mobileQuery;
4 |
--------------------------------------------------------------------------------
/core/client/public/assets/img/large.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/trheyi/Ghost/HEAD/core/client/public/assets/img/large.png
--------------------------------------------------------------------------------
/core/client/public/assets/img/medium.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/trheyi/Ghost/HEAD/core/client/public/assets/img/medium.png
--------------------------------------------------------------------------------
/core/client/public/assets/img/small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/trheyi/Ghost/HEAD/core/client/public/assets/img/small.png
--------------------------------------------------------------------------------
/core/test/utils/fixtures/app/badlib.js:
--------------------------------------------------------------------------------
1 | var knex = require('knex');
2 |
3 | module.exports = {
4 | knex: knex
5 | };
6 |
--------------------------------------------------------------------------------
/content/images/README.md:
--------------------------------------------------------------------------------
1 | # Content / Images
2 |
3 | If using the standard file storage, Ghost will upload images to this directory.
--------------------------------------------------------------------------------
/core/client/public/assets/fonts/icons.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/trheyi/Ghost/HEAD/core/client/public/assets/fonts/icons.woff
--------------------------------------------------------------------------------
/core/server/data/xml/sitemap/index.js:
--------------------------------------------------------------------------------
1 | var SiteMapManager = require('./manager');
2 |
3 | module.exports = new SiteMapManager();
4 |
--------------------------------------------------------------------------------
/core/test/utils/fixtures/app/goodlib.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | util: function () {
3 | return 42;
4 | }
5 | };
6 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "content/themes/casper"]
2 | path = content/themes/casper
3 | url = https://github.com/diancloud/Casper.git
4 |
--------------------------------------------------------------------------------
/core/client/lib/asset-delivery/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "asset-delivery",
3 | "keywords": [
4 | "ember-addon"
5 | ]
6 | }
7 |
--------------------------------------------------------------------------------
/core/client/public/assets/img/404-ghost.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/trheyi/Ghost/HEAD/core/client/public/assets/img/404-ghost.png
--------------------------------------------------------------------------------
/core/client/public/assets/img/loadingcat.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/trheyi/Ghost/HEAD/core/client/public/assets/img/loadingcat.gif
--------------------------------------------------------------------------------
/core/test/unit/server_helpers/test_tpl/pagination.hbs:
--------------------------------------------------------------------------------
1 | {{@blog.title}}
2 | Page {{page}} of {{pages}}
3 |
--------------------------------------------------------------------------------
/core/test/utils/fixtures/import/draft-2014-12-19-test-3.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | # Welcome to Ghost
4 |
5 | You're live! Nice.
--------------------------------------------------------------------------------
/core/client/app/templates/components/gh-activating-list-item.hbs:
--------------------------------------------------------------------------------
1 | {{#link-to route alternateActive=active}}{{title}}{{yield}}{{/link-to}}
2 |
--------------------------------------------------------------------------------
/core/client/public/assets/img/404-ghost@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/trheyi/Ghost/HEAD/core/client/public/assets/img/404-ghost@2x.png
--------------------------------------------------------------------------------
/core/client/app/utils/text-field.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | Ember.TextField.reopen({
3 | attributeBindings: ['autofocus']
4 | });
5 |
--------------------------------------------------------------------------------
/core/client/public/assets/img/touch-icon-ipad.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/trheyi/Ghost/HEAD/core/client/public/assets/img/touch-icon-ipad.png
--------------------------------------------------------------------------------
/core/client/app/templates/components/gh-notifications.hbs:
--------------------------------------------------------------------------------
1 | {{#each message in messages}}
2 | {{gh-notification message=message}}
3 | {{/each}}
4 |
--------------------------------------------------------------------------------
/core/client/app/validators/signup.js:
--------------------------------------------------------------------------------
1 | import NewUserValidator from 'ghost/validators/new-user';
2 |
3 | export default NewUserValidator.create();
4 |
--------------------------------------------------------------------------------
/core/client/public/assets/img/touch-icon-iphone.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/trheyi/Ghost/HEAD/core/client/public/assets/img/touch-icon-iphone.png
--------------------------------------------------------------------------------
/core/client/app/utils/ctrl-or-cmd.js:
--------------------------------------------------------------------------------
1 | var ctrlOrCmd = navigator.userAgent.indexOf('Mac') !== -1 ? 'command' : 'ctrl';
2 |
3 | export default ctrlOrCmd;
4 |
--------------------------------------------------------------------------------
/core/client/tests/test-helper.js:
--------------------------------------------------------------------------------
1 | import resolver from './helpers/resolver';
2 | import { setResolver } from 'ember-mocha';
3 |
4 | setResolver(resolver);
5 |
--------------------------------------------------------------------------------
/core/test/utils/fixtures/app/nested/goodnested.js:
--------------------------------------------------------------------------------
1 | /*jshint unused:false*/
2 | var lib = require('../goodlib.js');
3 |
4 | module.exports = {
5 | other: 42
6 | };
7 |
--------------------------------------------------------------------------------
/core/client/app/components/gh-blog-url.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 |
3 | var blogUrl = Ember.Component.extend({
4 | tagName: ''
5 | });
6 |
7 | export default blogUrl;
8 |
--------------------------------------------------------------------------------
/core/test/unit/server_helpers/test_tpl/navigation.hbs:
--------------------------------------------------------------------------------
1 | {{@blog.title}}
2 |
3 | {{#foreach navigation}}
4 | {{label}}
5 | {{/foreach}}
6 |
--------------------------------------------------------------------------------
/core/client/app/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/app/views/settings/labs.js:
--------------------------------------------------------------------------------
1 | import BaseView from 'ghost/views/settings/content-base';
2 |
3 | var SettingsLabsView = BaseView.extend();
4 |
5 | export default SettingsLabsView;
6 |
--------------------------------------------------------------------------------
/core/client/app/routes/settings/users.js:
--------------------------------------------------------------------------------
1 | import AuthenticatedRoute from 'ghost/routes/authenticated';
2 |
3 | var UsersRoute = AuthenticatedRoute.extend();
4 |
5 | export default UsersRoute;
6 |
--------------------------------------------------------------------------------
/core/client/app/views/settings/about.js:
--------------------------------------------------------------------------------
1 | import BaseView from 'ghost/views/settings/content-base';
2 |
3 | var SettingsAboutView = BaseView.extend();
4 |
5 | export default SettingsAboutView;
6 |
--------------------------------------------------------------------------------
/core/client/app/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/app/routes/authenticated.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | var AuthenticatedRoute = Ember.Route.extend(SimpleAuth.AuthenticatedRouteMixin);
3 |
4 | export default AuthenticatedRoute;
5 |
--------------------------------------------------------------------------------
/core/client/app/views/posts/post.js:
--------------------------------------------------------------------------------
1 | import MobileContentView from 'ghost/views/mobile/content-view';
2 |
3 | var PostsPostView = MobileContentView.extend();
4 |
5 | export default PostsPostView;
6 |
--------------------------------------------------------------------------------
/core/client/app/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/app/views/settings/index.js:
--------------------------------------------------------------------------------
1 | import MobileIndexView from 'ghost/views/mobile/index-view';
2 |
3 | var SettingsIndexView = MobileIndexView.extend();
4 |
5 | export default SettingsIndexView;
6 |
--------------------------------------------------------------------------------
/core/client/app/views/settings/pass-protect.js:
--------------------------------------------------------------------------------
1 | import BaseView from 'ghost/views/settings/content-base';
2 |
3 | var SettingsGeneralView = BaseView.extend();
4 |
5 | export default SettingsGeneralView;
6 |
--------------------------------------------------------------------------------
/core/client/app/templates/modals/delete-post.hbs:
--------------------------------------------------------------------------------
1 | {{#gh-modal-dialog action="closeModal" showClose=true type="action" style="wide"
2 | title="真的要删除这篇博文吗?" confirm=confirm}}
3 |
4 | {{/gh-modal-dialog}}
5 |
--------------------------------------------------------------------------------
/core/client/app/views/settings/code-injection.js:
--------------------------------------------------------------------------------
1 | import BaseView from 'ghost/views/settings/content-base';
2 |
3 | var SettingsGeneralView = BaseView.extend();
4 |
5 | export default SettingsGeneralView;
6 |
--------------------------------------------------------------------------------
/core/client/app/templates/-import-errors.hbs:
--------------------------------------------------------------------------------
1 | {{#if importErrors}}
2 |
3 | {{#each error in importErrors}}
4 | | {{error.message}} |
5 | {{/each}}
6 |
7 | {{/if}}
8 |
--------------------------------------------------------------------------------
/core/client/app/components/gh-input.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | import TextInputMixin from 'ghost/mixins/text-input';
3 |
4 | var Input = Ember.TextField.extend(TextInputMixin);
5 |
6 | export default Input;
7 |
--------------------------------------------------------------------------------
/core/client/app/templates/components/gh-role-selector.hbs:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/core/client/app/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/client/app/components/gh-textarea.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | import TextInputMixin from 'ghost/mixins/text-input';
3 |
4 | var TextArea = Ember.TextArea.extend(TextInputMixin);
5 |
6 | export default TextArea;
7 |
--------------------------------------------------------------------------------
/core/client/app/adapters/application.js:
--------------------------------------------------------------------------------
1 | import EmbeddedRelationAdapter from 'ghost/adapters/embedded-relation-adapter';
2 |
3 | var ApplicationAdapter = EmbeddedRelationAdapter.extend();
4 |
5 | export default ApplicationAdapter;
6 |
--------------------------------------------------------------------------------
/core/client/app/components/gh-popover.js:
--------------------------------------------------------------------------------
1 | import GhostDropdown from 'ghost/components/gh-dropdown';
2 |
3 | var GhostPopover = GhostDropdown.extend({
4 | classNames: 'ghost-popover'
5 | });
6 |
7 | export default GhostPopover;
8 |
--------------------------------------------------------------------------------
/core/client/app/routes/content.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | var ContentRoute = Ember.Route.extend({
3 | beforeModel: function () {
4 | this.transitionTo('posts');
5 | }
6 | });
7 |
8 | export default ContentRoute;
9 |
--------------------------------------------------------------------------------
/core/client/app/routes/debug.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | var DebugRoute = Ember.Route.extend({
3 | beforeModel: function () {
4 | this.transitionTo('settings.labs');
5 | }
6 | });
7 |
8 | export default DebugRoute;
9 |
--------------------------------------------------------------------------------
/core/client/app/views/posts/index.js:
--------------------------------------------------------------------------------
1 | import MobileIndexView from 'ghost/views/mobile/index-view';
2 |
3 | var PostsIndexView = MobileIndexView.extend({
4 | classNames: ['no-posts-box']
5 | });
6 |
7 | export default PostsIndexView;
8 |
--------------------------------------------------------------------------------
/core/client/app/routes/editor/index.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | var EditorRoute = Ember.Route.extend({
3 | beforeModel: function () {
4 | this.transitionTo('editor.new');
5 | }
6 | });
7 |
8 | export default EditorRoute;
9 |
--------------------------------------------------------------------------------
/core/client/app/templates/modals/delete-all.hbs:
--------------------------------------------------------------------------------
1 | {{#gh-modal-dialog action="closeModal" type="action" style="wide"
2 | title="真的要删除所有博文吗?" confirm=confirm}}
3 |
4 | 博文一旦删除可能将无法恢复,请陛下三思。
真的要删除吗?
5 |
6 | {{/gh-modal-dialog}}
7 |
--------------------------------------------------------------------------------
/core/client/app/templates/modals/transfer-owner.hbs:
--------------------------------------------------------------------------------
1 |
2 | {{#gh-modal-dialog action="closeModal" showClose=true type="action" style="wide"
3 | title="转让所有权" confirm=confirm}}
4 |
5 | 真的要将博客转让吗? 这个操作不可恢复。
6 |
7 | {{/gh-modal-dialog}}
8 |
--------------------------------------------------------------------------------
/core/client/testem.json:
--------------------------------------------------------------------------------
1 | {
2 | "framework": "qunit",
3 | "test_page": "tests/index.html?hidepassed",
4 | "launch_in_ci": [
5 | "PhantomJS"
6 | ],
7 | "launch_in_dev": [
8 | "PhantomJS",
9 | "Chrome"
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/core/server/helpers/tpl/navigation.hbs:
--------------------------------------------------------------------------------
1 |
2 | {{#foreach navigation}}
3 | - {{label}}
4 | {{/foreach}}
5 |
--------------------------------------------------------------------------------
/core/client/app/controllers/modals/copy-html.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | var CopyHTMLController = Ember.Controller.extend({
3 |
4 | generatedHTML: Ember.computed.alias('model.generatedHTML')
5 |
6 | });
7 |
8 | export default CopyHTMLController;
9 |
--------------------------------------------------------------------------------
/core/client/app/templates/posts/index.hbs:
--------------------------------------------------------------------------------
1 | {{#if noPosts}}
2 |
3 |
没有博文,赶快创作吧!
4 | {{#link-to "editor.new"}}{{/link-to}}
5 |
6 | {{/if}}
7 |
--------------------------------------------------------------------------------
/core/client/app/utils/isFinite.js:
--------------------------------------------------------------------------------
1 | /* globals window */
2 |
3 | // isFinite function from lodash
4 |
5 | function isFinite(value) {
6 | return window.isFinite(value) && !window.isNaN(parseFloat(value));
7 | }
8 |
9 | export default isFinite;
10 |
--------------------------------------------------------------------------------
/core/client/app/templates/modals/copy-html.hbs:
--------------------------------------------------------------------------------
1 | {{#gh-modal-dialog action="closeModal" showClose=true type="action"
2 | title="Generated HTML" confirm=confirm class="copy-html"}}
3 |
4 | {{textarea value=generatedHTML rows="6"}}
5 |
6 | {{/gh-modal-dialog}}
7 |
--------------------------------------------------------------------------------
/core/client/app/controllers/editor/edit.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | import EditorControllerMixin from 'ghost/mixins/editor-base-controller';
3 |
4 | var EditorEditController = Ember.Controller.extend(EditorControllerMixin);
5 |
6 | export default EditorEditController;
7 |
--------------------------------------------------------------------------------
/core/server/events/index.js:
--------------------------------------------------------------------------------
1 | var events = require('events'),
2 | util = require('util'),
3 | EventRegistry;
4 |
5 | EventRegistry = function () {};
6 |
7 | util.inherits(EventRegistry, events.EventEmitter);
8 |
9 | module.exports = new EventRegistry();
10 |
--------------------------------------------------------------------------------
/core/client/app/templates/modals/leave-editor.hbs:
--------------------------------------------------------------------------------
1 |
2 | {{#gh-modal-dialog action="closeModal" showClose=true type="action" style="wide"
3 | title="真的要离开这个页面吗?" confirm=confirm}}
4 |
5 | 刚刚做的修改还没有保存呢。
6 |
7 | 建议保存后离开!
8 |
9 | {{/gh-modal-dialog}}
10 |
--------------------------------------------------------------------------------
/core/client/app/utils/random-password.js:
--------------------------------------------------------------------------------
1 | /* global generatePassword */
2 |
3 | function randomPassword() {
4 | var word = generatePassword(6),
5 | randomN = Math.floor(Math.random() * 1000);
6 |
7 | return word + randomN;
8 | }
9 |
10 | export default randomPassword;
11 |
--------------------------------------------------------------------------------
/core/client/app/views/settings/tags.js:
--------------------------------------------------------------------------------
1 | import BaseView from 'ghost/views/settings/content-base';
2 | import PaginationScrollMixin from 'ghost/mixins/pagination-view-infinite-scroll';
3 |
4 | var SettingsTagsView = BaseView.extend(PaginationScrollMixin);
5 |
6 | export default SettingsTagsView;
7 |
--------------------------------------------------------------------------------
/core/server/data/utils/clients/index.js:
--------------------------------------------------------------------------------
1 | var sqlite3 = require('./sqlite3'),
2 | mysql = require('./mysql'),
3 | pg = require('./pg');
4 |
5 | module.exports = {
6 | sqlite3: sqlite3,
7 | mysql: mysql,
8 | pg: pg,
9 | postgres: pg,
10 | postgresql: pg
11 | };
12 |
--------------------------------------------------------------------------------
/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 | };
11 |
--------------------------------------------------------------------------------
/core/client/app/templates/-user-actions-menu.hbs:
--------------------------------------------------------------------------------
1 | {{#if view.canMakeOwner}}
2 |
3 | {{/if}}
4 | {{#if view.deleteUserActionIsVisible}}
5 |
6 | {{/if}}
--------------------------------------------------------------------------------
/core/client/app/templates/components/gh-notification.hbs:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{message.message}}
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/core/client/app/views/editor/edit.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | import EditorViewMixin from 'ghost/mixins/editor-base-view';
3 |
4 | var EditorView = Ember.View.extend(EditorViewMixin, {
5 | tagName: 'section',
6 | classNames: ['entry-container']
7 | });
8 |
9 | export default EditorView;
10 |
--------------------------------------------------------------------------------
/core/client/app/adapters/user.js:
--------------------------------------------------------------------------------
1 | import ApplicationAdapter from 'ghost/adapters/application';
2 |
3 | var UserAdapter = ApplicationAdapter.extend({
4 | find: function (store, type, id) {
5 | return this.findQuery(store, type, {id: id, status: 'all'});
6 | }
7 | });
8 |
9 | export default UserAdapter;
10 |
--------------------------------------------------------------------------------
/core/client/app/views/settings/users/users-list-view.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | import PaginationViewMixin from 'ghost/mixins/pagination-view-infinite-scroll';
3 |
4 | var UsersListView = Ember.View.extend(PaginationViewMixin, {
5 | classNames: ['js-users-list-view']
6 | });
7 |
8 | export default UsersListView;
9 |
--------------------------------------------------------------------------------
/core/test/utils/fixtures/app/badoutside.js:
--------------------------------------------------------------------------------
1 | var lib = require('../example');
2 |
3 | function BadApp(app) {
4 | this.app = app;
5 | }
6 |
7 | BadApp.prototype.install = function () {
8 | return lib.answer;
9 | };
10 |
11 | BadApp.prototype.activate = function () {
12 | };
13 |
14 | module.exports = BadApp;
15 |
--------------------------------------------------------------------------------
/core/client/app/templates/components/gh-file-upload.hbs:
--------------------------------------------------------------------------------
1 |
2 |
5 |
--------------------------------------------------------------------------------
/core/client/app/initializers/store-injector.js:
--------------------------------------------------------------------------------
1 | var StoreInjector = {
2 | name: 'store-injector',
3 | after: 'store',
4 |
5 | initialize: function (container, application) {
6 | application.inject('component:gh-role-selector', 'store', 'store:main');
7 | }
8 | };
9 |
10 | export default StoreInjector;
11 |
--------------------------------------------------------------------------------
/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/client/.ember-cli:
--------------------------------------------------------------------------------
1 | {
2 | /**
3 | Ember CLI sends analytics information by default. The data is completely
4 | anonymous, but there are times when you might want to disable this behavior.
5 |
6 | Setting `disableAnalytics` to true will prevent any data from being sent.
7 | */
8 | "disableAnalytics": true
9 | }
10 |
--------------------------------------------------------------------------------
/core/test/utils/fixtures/app/badtop.js:
--------------------------------------------------------------------------------
1 | var knex = require('knex');
2 |
3 | function BadApp(app) {
4 | this.app = app;
5 | }
6 |
7 | BadApp.prototype.install = function () {
8 | return knex.dropTableIfExists('users');
9 | };
10 |
11 | BadApp.prototype.activate = function () {
12 | };
13 |
14 | module.exports = BadApp;
15 |
--------------------------------------------------------------------------------
/core/client/app/models/notification.js:
--------------------------------------------------------------------------------
1 | import DS from 'ember-data';
2 | var Notification = DS.Model.extend({
3 | dismissible: DS.attr('boolean'),
4 | location: DS.attr('string'),
5 | status: DS.attr('string'),
6 | type: DS.attr('string'),
7 | message: DS.attr('string')
8 | });
9 |
10 | export default Notification;
11 |
--------------------------------------------------------------------------------
/core/client/app/utils/isNumber.js:
--------------------------------------------------------------------------------
1 | // isNumber function from lodash
2 |
3 | var toString = Object.prototype.toString;
4 |
5 | function isNumber(value) {
6 | return typeof value === 'number' ||
7 | value && typeof value === 'object' && toString.call(value) === '[object Number]' || false;
8 | }
9 |
10 | export default isNumber;
11 |
--------------------------------------------------------------------------------
/core/client/tests/helpers/resolver.js:
--------------------------------------------------------------------------------
1 | import Resolver from 'ember/resolver';
2 | import config from '../../config/environment';
3 |
4 | var resolver = Resolver.create();
5 |
6 | resolver.namespace = {
7 | modulePrefix: config.modulePrefix,
8 | podModulePrefix: config.podModulePrefix
9 | };
10 |
11 | export default resolver;
12 |
--------------------------------------------------------------------------------
/core/test/utils/fixtures/app/badinstall.js:
--------------------------------------------------------------------------------
1 | function BadApp(app) {
2 | this.app = app;
3 | }
4 |
5 | BadApp.prototype.install = function () {
6 | var knex = require('knex');
7 |
8 | return knex.dropTableIfExists('users');
9 | };
10 |
11 | BadApp.prototype.activate = function () {
12 | };
13 |
14 | module.exports = BadApp;
15 |
--------------------------------------------------------------------------------
/core/client/app/templates/components/gh-uploader.hbs:
--------------------------------------------------------------------------------
1 |
2 | 单击添加图片
3 |
4 |
5 | {{description}}
6 |
7 |
--------------------------------------------------------------------------------
/core/server/routes/admin.js:
--------------------------------------------------------------------------------
1 | var admin = require('../controllers/admin'),
2 | express = require('express'),
3 |
4 | adminRoutes;
5 |
6 | adminRoutes = function () {
7 | var router = express.Router();
8 |
9 | router.get('*', admin.index);
10 |
11 | return router;
12 | };
13 |
14 | module.exports = adminRoutes;
15 |
--------------------------------------------------------------------------------
/core/test/utils/fixtures/app/badrequire.js:
--------------------------------------------------------------------------------
1 | var lib = require('./badlib');
2 |
3 | function BadApp(app) {
4 | this.app = app;
5 | }
6 |
7 | BadApp.prototype.install = function () {
8 | return lib.knex.dropTableIfExists('users');
9 | };
10 |
11 | BadApp.prototype.activate = function () {
12 | };
13 |
14 | module.exports = BadApp;
15 |
--------------------------------------------------------------------------------
/core/client/app/views/editor/new.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | import EditorViewMixin from 'ghost/mixins/editor-base-view';
3 |
4 | var EditorNewView = Ember.View.extend(EditorViewMixin, {
5 | tagName: 'section',
6 | templateName: 'editor/edit',
7 | classNames: ['entry-container']
8 | });
9 |
10 | export default EditorNewView;
11 |
--------------------------------------------------------------------------------
/core/client/tests/unit/models/tag_test.js:
--------------------------------------------------------------------------------
1 | import {
2 | describeModel,
3 | it
4 | } from 'ember-mocha';
5 |
6 | describeModel('tag', function () {
7 | it('has a validation type of "tag"', function () {
8 | var model = this.subject();
9 |
10 | expect(model.get('validationType')).to.equal('tag');
11 | });
12 | });
13 |
--------------------------------------------------------------------------------
/core/client/app/routes/error404.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | var Error404Route = Ember.Route.extend({
3 | controllerName: 'error',
4 | templateName: 'error',
5 | titleToken: 'Error',
6 |
7 | model: function () {
8 | return {
9 | status: 404
10 | };
11 | }
12 | });
13 |
14 | export default Error404Route;
15 |
--------------------------------------------------------------------------------
/core/client/tests/unit/models/setting_test.js:
--------------------------------------------------------------------------------
1 | import {
2 | describeModel,
3 | it
4 | } from 'ember-mocha';
5 |
6 | describeModel('setting', function () {
7 | it('has a validation type of "setting"', function () {
8 | var model = this.subject();
9 |
10 | expect(model.get('validationType')).to.equal('setting');
11 | });
12 | });
13 |
--------------------------------------------------------------------------------
/core/server/helpers/tpl/pagination.hbs:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/core/client/app/components/gh-activating-list-item.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | var ActivatingListItem = Ember.Component.extend({
3 | tagName: 'li',
4 | classNameBindings: ['active'],
5 | active: false,
6 |
7 | unfocusLink: function () {
8 | this.$('a').blur();
9 | }.on('click')
10 | });
11 |
12 | export default ActivatingListItem;
13 |
--------------------------------------------------------------------------------
/core/server/utils/sequence.js:
--------------------------------------------------------------------------------
1 | var Promise = require('bluebird');
2 |
3 | function sequence(tasks) {
4 | return Promise.reduce(tasks, function (results, task) {
5 | return task().then(function (result) {
6 | results.push(result);
7 |
8 | return results;
9 | });
10 | }, []);
11 | }
12 |
13 | module.exports = sequence;
14 |
--------------------------------------------------------------------------------
/core/client/app/utils/bind.js:
--------------------------------------------------------------------------------
1 | var slice = Array.prototype.slice;
2 |
3 | function bind(/* func, args, thisArg */) {
4 | var args = slice.call(arguments),
5 | func = args.shift(),
6 | thisArg = args.pop();
7 |
8 | function bound() {
9 | return func.apply(thisArg, args);
10 | }
11 |
12 | return bound;
13 | }
14 |
15 | export default bind;
16 |
--------------------------------------------------------------------------------
/core/index.js:
--------------------------------------------------------------------------------
1 | // # Ghost bootloader
2 | // Orchestrates the loading of Ghost
3 | // When run from command line.
4 |
5 | var server = require('./server');
6 |
7 | process.env.NODE_ENV = process.env.NODE_ENV || 'development';
8 |
9 | function makeGhost(options) {
10 | options = options || {};
11 |
12 | return server(options);
13 | }
14 |
15 | module.exports = makeGhost;
16 |
--------------------------------------------------------------------------------
/core/client/app/controllers/settings/about.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | var SettingsAboutController = Ember.Controller.extend({
3 | updateNotificationCount: 0,
4 |
5 | actions: {
6 | updateNotificationChange: function (count) {
7 | this.set('updateNotificationCount', count);
8 | }
9 | }
10 | });
11 |
12 | export default SettingsAboutController;
13 |
--------------------------------------------------------------------------------
/core/client/lib/asset-delivery/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | name: 'asset-delivery',
3 | postBuild: function (results) {
4 | var fs = this.project.require('fs-extra');
5 |
6 | fs.copySync(results.directory + '/index.html', '../server/views/default.hbs');
7 | fs.copySync('./dist/assets', '../built/assets');
8 | }
9 | };
10 |
--------------------------------------------------------------------------------
/core/client/app/routes/forgotten.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | import styleBody from 'ghost/mixins/style-body';
3 | import loadingIndicator from 'ghost/mixins/loading-indicator';
4 |
5 | var ForgottenRoute = Ember.Route.extend(styleBody, loadingIndicator, {
6 | titleToken: 'Forgotten Password',
7 |
8 | classNames: ['ghost-forgotten']
9 | });
10 |
11 | export default ForgottenRoute;
12 |
--------------------------------------------------------------------------------
/core/server/helpers/encode.js:
--------------------------------------------------------------------------------
1 | // # Encode Helper
2 | //
3 | // Usage: `{{encode uri}}`
4 | //
5 | // Returns URI encoded string
6 |
7 | var hbs = require('express-hbs'),
8 | encode;
9 |
10 | encode = function (context, str) {
11 | var uri = context || str;
12 | return new hbs.handlebars.SafeString(encodeURIComponent(uri));
13 | };
14 |
15 | module.exports = encode;
16 |
--------------------------------------------------------------------------------
/core/client/app/_config.yml:
--------------------------------------------------------------------------------
1 | # Dependencies
2 | markdown: kramdown
3 | highlighter: highlighter
4 |
5 | # Permalinks
6 | permalink: pretty
7 |
8 | # Server
9 | source: docs
10 | destination: _gh_pages
11 | host: 0.0.0.0
12 | port: 9001
13 | baseurl:
14 | url: http://localhost:9001
15 | encoding: UTF-8
16 |
--------------------------------------------------------------------------------
/core/test/utils/fixtures/export/export-003-minimal.json:
--------------------------------------------------------------------------------
1 | {
2 | "meta": {
3 | "exported_on": 1388318311015,
4 | "version": "003"
5 | },
6 | "data": {
7 | "posts": [
8 | {
9 | "title": "Welcome to Ghost",
10 | "slug": "welcome-to-ghost",
11 | "markdown": "You're live! Nice."
12 | }
13 | ]
14 | }
15 | }
--------------------------------------------------------------------------------
/core/client/app/templates/modals/upload.hbs:
--------------------------------------------------------------------------------
1 | {{#gh-upload-modal action="closeModal" close=true type="action" style="wide" model=model imageType=imageType}}
2 |
6 |
7 | {{/gh-upload-modal}}
8 |
--------------------------------------------------------------------------------
/core/client/app/routes/settings.js:
--------------------------------------------------------------------------------
1 | import AuthenticatedRoute from 'ghost/routes/authenticated';
2 | import styleBody from 'ghost/mixins/style-body';
3 | import loadingIndicator from 'ghost/mixins/loading-indicator';
4 |
5 | var SettingsRoute = AuthenticatedRoute.extend(styleBody, loadingIndicator, {
6 | titleToken: 'Settings',
7 |
8 | classNames: ['settings']
9 | });
10 |
11 | export default SettingsRoute;
12 |
--------------------------------------------------------------------------------
/core/server/helpers/title.js:
--------------------------------------------------------------------------------
1 | // # Title Helper
2 | // Usage: `{{title}}`
3 | //
4 | // Overrides the standard behaviour of `{[title}}` to ensure the content is correctly escaped
5 |
6 | var hbs = require('express-hbs'),
7 | title;
8 |
9 | title = function () {
10 | return new hbs.handlebars.SafeString(hbs.handlebars.Utils.escapeExpression(this.title || ''));
11 | };
12 |
13 | module.exports = title;
14 |
--------------------------------------------------------------------------------
/core/server/models/client.js:
--------------------------------------------------------------------------------
1 | var ghostBookshelf = require('./base'),
2 |
3 | Client,
4 | Clients;
5 |
6 | Client = ghostBookshelf.Model.extend({
7 | tableName: 'clients'
8 | });
9 |
10 | Clients = ghostBookshelf.Collection.extend({
11 | model: Client
12 | });
13 |
14 | module.exports = {
15 | Client: ghostBookshelf.model('Client', Client),
16 | Clients: ghostBookshelf.collection('Clients', Clients)
17 | };
18 |
--------------------------------------------------------------------------------
/core/client/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # compiled output
4 | /dist
5 | /tmp
6 |
7 | # dependencies
8 | /node_modules
9 | /bower_components
10 |
11 | # misc
12 | /.sass-cache
13 | /connect.lock
14 | /coverage/*
15 | /libpeerconnection.log
16 | npm-debug.log
17 | testem.log
18 |
19 | # built by grunt
20 | public/assets/img/contributors/
21 | app/templates/-contributors.hbs
22 |
--------------------------------------------------------------------------------
/core/server/data/importer/importers/data.js:
--------------------------------------------------------------------------------
1 | var importer = require('../../import'),
2 | DataImporter;
3 |
4 | DataImporter = {
5 | type: 'data',
6 | preProcess: function (importData) {
7 | importData.preProcessedByData = true;
8 | return importData;
9 | },
10 | doImport: function (importData) {
11 | return importer.doImport(importData);
12 | }
13 | };
14 |
15 | module.exports = DataImporter;
16 |
--------------------------------------------------------------------------------
/core/server/errors/email-error.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.errorType = this.name;
9 | }
10 |
11 | EmailError.prototype = Object.create(Error.prototype);
12 | EmailError.prototype.name = 'EmailError';
13 |
14 | module.exports = EmailError;
15 |
--------------------------------------------------------------------------------
/core/client/app/utils/validator-extensions.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | function init() {
3 | // Provide a few custom validators
4 | //
5 | validator.extend('empty', function (str) {
6 | return Ember.isBlank(str);
7 | });
8 |
9 | validator.extend('notContains', function (str, badString) {
10 | return str.indexOf(badString) === -1;
11 | });
12 | }
13 |
14 | export default {
15 | init: init
16 | };
17 |
--------------------------------------------------------------------------------
/core/server/errors/not-found-error.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.errorType = this.name;
9 | }
10 |
11 | NotFoundError.prototype = Object.create(Error.prototype);
12 | NotFoundError.prototype.name = 'NotFoundError';
13 |
14 | module.exports = NotFoundError;
15 |
--------------------------------------------------------------------------------
/core/client/app/mixins/dropdown-mixin.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | /*
3 | Dropdowns and their buttons are evented and do not propagate clicks.
4 | */
5 | var DropdownMixin = Ember.Mixin.create(Ember.Evented, {
6 | classNameBindings: ['isOpen:open:closed'],
7 | isOpen: false,
8 |
9 | click: function (event) {
10 | this._super(event);
11 |
12 | return event.stopPropagation();
13 | }
14 | });
15 |
16 | export default DropdownMixin;
17 |
--------------------------------------------------------------------------------
/core/client/app/views/mobile/index-view.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | import mobileQuery from 'ghost/utils/mobile';
3 |
4 | var MobileIndexView = Ember.View.extend({
5 | // Ensure that going to the index brings the menu into view on mobile.
6 | showMenu: function () {
7 | if (mobileQuery.matches) {
8 | this.get('parentView').showMenu();
9 | }
10 | }.on('didInsertElement')
11 | });
12 |
13 | export default MobileIndexView;
14 |
--------------------------------------------------------------------------------
/core/server/data/xml/sitemap/utils.js:
--------------------------------------------------------------------------------
1 | var config = require('../../../config'),
2 | utils;
3 |
4 | utils = {
5 | getDeclarations: function () {
6 | var baseUrl = config.urlFor('sitemap-xsl');
7 | baseUrl = baseUrl.replace(/^(http:|https:)/, '');
8 | return '' +
9 | '';
10 | }
11 | };
12 |
13 | module.exports = utils;
14 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 |
3 | root = true
4 |
5 | [*]
6 | charset = utf-8
7 | indent_style = space
8 | indent_size = 4
9 | end_of_line = lf
10 | insert_final_newline = true
11 | trim_trailing_whitespace = true
12 |
13 | [*.hbs]
14 | insert_final_newline = false
15 |
16 | [{package,bower}.json]
17 | indent_size = 2
18 |
19 | [*.md]
20 | trim_trailing_whitespace = false
21 |
22 | [*.yml]
23 | indent_size = 2
24 |
25 | [Makefile]
26 | indent_style = tab
27 |
--------------------------------------------------------------------------------
/core/client/app/views/mobile/content-view.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | import mobileQuery from 'ghost/utils/mobile';
3 |
4 | var MobileContentView = Ember.View.extend({
5 | // Ensure that loading this view brings it into view on mobile
6 | showContent: function () {
7 | if (mobileQuery.matches) {
8 | this.get('parentView').showContent();
9 | }
10 | }.on('didInsertElement')
11 | });
12 |
13 | export default MobileContentView;
14 |
--------------------------------------------------------------------------------
/core/server/errors/bad-request-error.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.errorType = this.name;
9 | }
10 |
11 | BadRequestError.prototype = Object.create(Error.prototype);
12 | BadRequestError.prototype.name = 'BadRequestError';
13 |
14 | module.exports = BadRequestError;
15 |
--------------------------------------------------------------------------------
/core/client/app/utils/link-view.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | Ember.LinkView.reopen({
3 | active: Ember.computed('loadedParams', 'resolvedParams', 'routeArgs', function () {
4 | var isActive = this._super();
5 |
6 | Ember.set(this, 'alternateActive', isActive);
7 |
8 | return isActive;
9 | }),
10 |
11 | activeClass: Ember.computed('tagName', function () {
12 | return this.get('tagName') === 'button' ? '' : 'active';
13 | })
14 | });
15 |
--------------------------------------------------------------------------------
/core/server/errors/unauthorized-error.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.errorType = this.name;
9 | }
10 |
11 | UnauthorizedError.prototype = Object.create(Error.prototype);
12 | UnauthorizedError.prototype.name = 'UnauthorizedError';
13 |
14 | module.exports = UnauthorizedError;
15 |
--------------------------------------------------------------------------------
/core/server/errors/no-permission-error.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.errorType = this.name;
9 | }
10 |
11 | NoPermissionError.prototype = Object.create(Error.prototype);
12 | NoPermissionError.prototype.name = 'NoPermissionError';
13 |
14 | module.exports = NoPermissionError;
15 |
--------------------------------------------------------------------------------
/core/server/errors/internal-server-error.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.errorType = this.name;
9 | }
10 |
11 | InternalServerError.prototype = Object.create(Error.prototype);
12 | InternalServerError.prototype.name = 'InternalServerError';
13 |
14 | module.exports = InternalServerError;
15 |
--------------------------------------------------------------------------------
/core/client/app/components/gh-form.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | var Form = Ember.View.extend({
3 | tagName: 'form',
4 | attributeBindings: ['enctype'],
5 | reset: function () {
6 | this.$().get(0).reset();
7 | },
8 | didInsertElement: function () {
9 | this.get('controller').on('reset', this, this.reset);
10 | },
11 | willClearRender: function () {
12 | this.get('controller').off('reset', this, this.reset);
13 | }
14 | });
15 |
16 | export default Form;
17 |
--------------------------------------------------------------------------------
/core/client/app/validators/forgotten.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | var ForgotValidator = Ember.Object.create({
3 | check: function (model) {
4 | var data = model.getProperties('email'),
5 | validationErrors = [];
6 |
7 | if (!validator.isEmail(data.email)) {
8 | validationErrors.push({
9 | message: '邮箱格式不正确。'
10 | });
11 | }
12 |
13 | return validationErrors;
14 | }
15 | });
16 |
17 | export default ForgotValidator;
18 |
--------------------------------------------------------------------------------
/core/client/app/templates/application.hbs:
--------------------------------------------------------------------------------
1 | Skip to main content
2 |
3 | {{#unless hideNav}}
4 | {{partial "navbar"}}
5 | {{/unless}}
6 |
7 | {{gh-notifications location="top" notify="topNotificationChange"}}
8 |
9 |
10 | {{outlet}}
11 | {{gh-notifications location="bottom"}}
12 |
13 |
14 | {{outlet "modal"}}
15 |
16 | {{outlet "settings-menu"}}
17 |
--------------------------------------------------------------------------------
/core/client/app/templates/modals/delete-tag.hbs:
--------------------------------------------------------------------------------
1 | {{#gh-modal-dialog action="closeModal" showClose=true type="action" style="wide"
2 | title="请确认删除这个标签?" confirm=confirm}}
3 |
4 | {{#if model.post_count}}
5 | 警告: 这个标签被 {{model.post_count}} {{postInflection}} 使用。
6 | 你将删除 "{{model.name}}" 标签. 此操作不可恢复,请确认。
7 | {{else}}
8 | 警告: 你将删除 "{{model.name}}" 标签. 此操作不可恢复,请确认。
9 | {{/if}}
10 | {{/gh-modal-dialog}}
11 |
--------------------------------------------------------------------------------
/core/client/app/templates/-publish-bar.hbs:
--------------------------------------------------------------------------------
1 |
13 |
--------------------------------------------------------------------------------
/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 | };
19 |
--------------------------------------------------------------------------------
/core/server/errors/unsupported-media-type-error.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.errorType = this.name;
9 | }
10 |
11 | UnsupportedMediaTypeError.prototype = Object.create(Error.prototype);
12 | UnsupportedMediaTypeError.prototype.name = 'UnsupportedMediaTypeError';
13 |
14 | module.exports = UnsupportedMediaTypeError;
15 |
--------------------------------------------------------------------------------
/core/client/app/models/role.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | import DS from 'ember-data';
3 | var Role = DS.Model.extend({
4 | uuid: DS.attr('string'),
5 | name: DS.attr('string'),
6 | description: DS.attr('string'),
7 | created_at: DS.attr('moment-date'),
8 | updated_at: DS.attr('moment-date'),
9 | created_by: DS.attr(),
10 | updated_by: DS.attr(),
11 |
12 | lowerCaseName: Ember.computed('name', function () {
13 | return this.get('name').toLocaleLowerCase();
14 | })
15 | });
16 |
17 | export default Role;
18 |
--------------------------------------------------------------------------------
/core/server/errors/request-too-large-error.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.errorType = this.name;
9 | }
10 |
11 | RequestEntityTooLargeError.prototype = Object.create(Error.prototype);
12 | RequestEntityTooLargeError.prototype.name = 'RequestEntityTooLargeError';
13 |
14 | module.exports = RequestEntityTooLargeError;
15 |
--------------------------------------------------------------------------------
/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 | };
19 |
--------------------------------------------------------------------------------
/core/server/helpers/utils.js:
--------------------------------------------------------------------------------
1 | var _ = require('lodash'),
2 | utils;
3 |
4 | utils = {
5 | assetTemplate: _.template('<%= source %>?v=<%= version %>'),
6 | linkTemplate: _.template('<%= text %>'),
7 | scriptTemplate: _.template(''),
8 | inputTemplate: _.template(' />'),
9 | isProduction: process.env.NODE_ENV === 'production'
10 | };
11 |
12 | module.exports = utils;
13 |
--------------------------------------------------------------------------------
/core/server/models/app-field.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 | };
21 |
--------------------------------------------------------------------------------
/core/client/app/templates/forgotten.hbs:
--------------------------------------------------------------------------------
1 |
10 |
--------------------------------------------------------------------------------
/core/server/models/app-setting.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 | };
21 |
--------------------------------------------------------------------------------
/core/client/app/templates/modals/delete-user.hbs:
--------------------------------------------------------------------------------
1 |
2 | {{#gh-modal-dialog action="closeModal" showClose=true type="action" style="wide"
3 | title="真的要删除这个用户吗?" confirm=confirm}}
4 |
5 | {{#unless userPostCount.isPending}}
6 | {{#if userPostCount.count}}
7 | 警告:该用户发表的博文( {{userPostCount.count}} {{userPostCount.inflection}}. )等信息将一起被删除。 这些数据将无法恢复。
8 | {{else}}
9 | 警告: 该用户发表的博文等信息将一起被删除。 这些数据将无法恢复。
10 | {{/if}}
11 | {{/unless}}
12 |
13 | {{/gh-modal-dialog}}
14 |
--------------------------------------------------------------------------------
/core/client/app/views/settings/content-base.js:
--------------------------------------------------------------------------------
1 | import MobileContentView from 'ghost/views/mobile/content-view';
2 | /**
3 | * All settings views other than the index should inherit from this base class.
4 | * It ensures that the correct screen is showing when a mobile user navigates
5 | * to a `settings.someRouteThatIsntIndex` route.
6 | */
7 |
8 | var SettingsContentBaseView = MobileContentView.extend({
9 | tagName: 'section',
10 | classNames: ['settings-content', 'js-settings-content', 'fade-in']
11 | });
12 |
13 | export default SettingsContentBaseView;
14 |
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "node": true,
3 | "browser": false,
4 | "strict": false,
5 | "sub": true,
6 | "eqeqeq": true,
7 | "laxbreak": true,
8 | "bitwise": true,
9 | "curly": true,
10 | "forin": true,
11 | "immed": true,
12 | "latedef": true,
13 | "newcap": true,
14 | "noarg": true,
15 | "noempty": true,
16 | "nonew": true,
17 | "plusplus": true,
18 | "regexp": true,
19 | "undef": true,
20 | "unused": true,
21 | "indent": 4,
22 | "predef": [ "-Promise" ]
23 | }
24 |
--------------------------------------------------------------------------------
/core/client/app/app.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | import Resolver from 'ember/resolver';
3 | import loadInitializers from 'ember/load-initializers';
4 | import 'ghost/utils/link-view';
5 | import 'ghost/utils/text-field';
6 | import config from './config/environment';
7 |
8 | Ember.MODEL_FACTORY_INJECTIONS = true;
9 |
10 | var App = Ember.Application.extend({
11 | modulePrefix: config.modulePrefix,
12 | podModulePrefix: config.podModulePrefix,
13 | Resolver: Resolver
14 | });
15 |
16 | loadInitializers(App, config.modulePrefix);
17 |
18 | export default App;
19 |
--------------------------------------------------------------------------------
/core/client/app/styles/components/url-preview.scss:
--------------------------------------------------------------------------------
1 | // ------------------------------------------------------------
2 | // URL Preview
3 | //
4 | // Styles for the {{url-preview}} component
5 | //
6 | // * Overflow Ellipsis
7 | // ------------------------------------------------------------
8 |
9 | //
10 | // Overflow Ellipsis
11 | // --------------------------------------------------
12 |
13 | .ghost-url-preview {
14 | width: 98%; // Makes sure the preview isnt wider than the input
15 | white-space: nowrap;
16 | overflow: hidden;
17 | text-overflow: ellipsis;
18 | }
--------------------------------------------------------------------------------
/core/server/helpers/image.js:
--------------------------------------------------------------------------------
1 |
2 | // Usage: `{{image}}`, `{{image absolute="true"}}`
3 | //
4 | // Returns the URL for the current object scope i.e. If inside a post scope will return image permalink
5 | // `absolute` flag outputs absolute URL, else URL is relative.
6 |
7 | var config = require('../config'),
8 | image;
9 |
10 | image = function (options) {
11 | var absolute = options && options.hash.absolute;
12 |
13 | if (this.image) {
14 | return config.urlFor('image', {image: this.image}, absolute);
15 | }
16 | };
17 |
18 | module.exports = image;
19 |
--------------------------------------------------------------------------------
/core/client/app/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: '请填写博客名称。'
11 | });
12 | }
13 |
14 | return validationErrors;
15 | }
16 | }).create();
17 |
18 | export default SetupValidator;
19 |
--------------------------------------------------------------------------------
/core/server/errors/validation-error.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.errorType = this.name;
12 | }
13 |
14 | ValidationError.prototype = Object.create(Error.prototype);
15 | ValidationError.prototype.name = 'ValidationError';
16 |
17 | module.exports = ValidationError;
18 |
--------------------------------------------------------------------------------
/core/test/utils/fixtures/app/good.js:
--------------------------------------------------------------------------------
1 | var path = require('path'),
2 | util = require('./goodlib.js'),
3 | nested = require('./nested/goodnested');
4 |
5 | function GoodApp(app) {
6 | this.app = app;
7 | }
8 |
9 | GoodApp.prototype.install = function () {
10 | // Goes through app to do data
11 | this.app.something = 42;
12 | this.app.util = util;
13 | this.app.nested = nested;
14 | this.app.path = path.join(__dirname, 'good.js');
15 |
16 | return true;
17 | };
18 |
19 | GoodApp.prototype.activate = function () {
20 | };
21 |
22 | module.exports = GoodApp;
23 |
--------------------------------------------------------------------------------
/core/client/app/helpers/gh-count-words.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | import counter from 'ghost/utils/word-count';
3 |
4 | var countWords = Ember.HTMLBars.makeBoundHelper(function (arr /* hashParams */) {
5 | if (!arr || !arr.length) {
6 | return;
7 | }
8 |
9 | var markdown,
10 | count;
11 |
12 | markdown = arr[0] || '';
13 |
14 | if (/^\s*$/.test(markdown)) {
15 | return '0 个字';
16 | }
17 |
18 | count = counter(markdown);
19 |
20 | return count + (count === 1 ? ' 个字' : ' 个字');
21 |
22 | });
23 |
24 | export default countWords;
25 |
--------------------------------------------------------------------------------
/core/server/errors/data-import-error.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.errorType = 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 | module.exports = DataImportError;
17 |
--------------------------------------------------------------------------------
/.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 | **/*.db*
17 | *.db*
18 | .sass*
19 | .af*
20 | .git*
21 | .groc*
22 | .jshintrc
23 | *.iml
24 | config.js
25 | core/built/**/*.map
26 | core/client/**
27 | core/test/**
28 | CONTRIBUTING.md
29 | SECURITY.md
30 | .travis.yml
31 | *.html
32 | !core/server/email-templates/**
33 | bower_components/**
34 | .editorconfig
35 |
--------------------------------------------------------------------------------
/core/client/app/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/test/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "node": true,
3 | "browser": true,
4 | "strict": false,
5 | "sub": true,
6 | "eqeqeq": true,
7 | "laxbreak": true,
8 | "bitwise": true,
9 | "curly": true,
10 | "forin": true,
11 | "immed": true,
12 | "latedef": true,
13 | "newcap": true,
14 | "noarg": true,
15 | "noempty": true,
16 | "nonew": true,
17 | "plusplus": true,
18 | "regexp": true,
19 | "undef": true,
20 | "unused": true,
21 | "indent": 4,
22 | "quotmark": "single",
23 | "predef": [ "-Promise" ]
24 | }
25 |
--------------------------------------------------------------------------------
/core/client/app/validators/signin.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | var SigninValidator = Ember.Object.create({
3 | check: function (model) {
4 | var data = model.getProperties('identification', 'password'),
5 | validationErrors = [];
6 |
7 | if (!validator.isEmail(data.identification)) {
8 | validationErrors.push('邮箱格式不正确');
9 | }
10 |
11 | if (!validator.isLength(data.password || '', 1)) {
12 | validationErrors.push('请输入密码');
13 | }
14 |
15 | return validationErrors;
16 | }
17 | });
18 |
19 | export default SigninValidator;
20 |
--------------------------------------------------------------------------------
/core/client/app/controllers/error.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | var ErrorController = Ember.Controller.extend({
3 | code: Ember.computed('content.status', function () {
4 | return this.get('content.status') > 200 ? this.get('content.status') : 500;
5 | }),
6 | message: Ember.computed('content.statusText', function () {
7 | if (this.get('code') === 404) {
8 | return 'No Ghost Found';
9 | }
10 |
11 | return this.get('content.statusText') !== 'error' ? this.get('content.statusText') : 'Internal Server Error';
12 | }),
13 | stack: false
14 | });
15 |
16 | export default ErrorController;
17 |
--------------------------------------------------------------------------------
/core/client/app/mixins/nprogress-save.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | var NProgressSaveMixin = Ember.Mixin.create({
3 | save: function (options) {
4 | if (options && options.disableNProgress) {
5 | return this._super(options);
6 | }
7 |
8 | NProgress.start();
9 |
10 | return this._super(options).then(function (value) {
11 | NProgress.done();
12 |
13 | return value;
14 | }).catch(function (error) {
15 | NProgress.done();
16 |
17 | return Ember.RSVP.reject(error);
18 | });
19 | }
20 | });
21 |
22 | export default NProgressSaveMixin;
23 |
--------------------------------------------------------------------------------
/core/client/app/utils/titleize.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | var lowerWords = ['of', 'a', 'the', 'and', 'an', 'or', 'nor', 'but', 'is', 'if',
3 | 'then', 'else', 'when', 'at', 'from', 'by', 'on', 'off', 'for',
4 | 'in', 'out', 'over', 'to', 'into', 'with'];
5 |
6 | function titleize(input) {
7 | var words = input.split(' ').map(function (word, index) {
8 | if (index === 0 || lowerWords.indexOf(word) === -1) {
9 | word = Ember.String.capitalize(word);
10 | }
11 |
12 | return word;
13 | });
14 |
15 | return words.join(' ');
16 | }
17 |
18 | export default titleize;
19 |
--------------------------------------------------------------------------------
/core/client/app/components/gh-dropdown-button.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | import DropdownMixin from 'ghost/mixins/dropdown-mixin';
3 |
4 | var DropdownButton = Ember.Component.extend(DropdownMixin, {
5 | tagName: 'button',
6 | attributeBindings: 'role',
7 | role: 'button',
8 |
9 | // matches with the dropdown this button toggles
10 | dropdownName: null,
11 |
12 | // Notify dropdown service this dropdown should be toggled
13 | click: function (event) {
14 | this._super(event);
15 | this.get('dropdown').toggleDropdown(this.get('dropdownName'), this);
16 | }
17 | });
18 |
19 | export default DropdownButton;
20 |
--------------------------------------------------------------------------------
/core/server/utils/pipeline.js:
--------------------------------------------------------------------------------
1 | var Promise = require('bluebird');
2 |
3 | function pipeline(tasks /* initial arguments */) {
4 | var args = Array.prototype.slice.call(arguments, 1),
5 |
6 | runTask = function (task, args) {
7 | runTask = function (task, arg) {
8 | return task(arg);
9 | };
10 |
11 | return task.apply(null, args);
12 | };
13 |
14 | return Promise.all(tasks).reduce(function (arg, task) {
15 | return Promise.resolve(runTask(task, arg)).then(function (result) {
16 | return result;
17 | });
18 | }, args);
19 | }
20 |
21 | module.exports = pipeline;
22 |
--------------------------------------------------------------------------------
/core/client/app/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 | 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 | id = function (id) {
23 | return id;
24 | };
25 |
26 | export default {
27 | url: url,
28 | id: id
29 | };
30 |
--------------------------------------------------------------------------------
/core/client/app/initializers/ghost-config.js:
--------------------------------------------------------------------------------
1 | import getConfig from 'ghost/utils/config-parser';
2 |
3 | var ConfigInitializer = {
4 | name: 'config',
5 |
6 | initialize: function (container, application) {
7 | var config = getConfig();
8 | application.register('ghost:config', config, {instantiate: false});
9 |
10 | application.inject('route', 'config', 'ghost:config');
11 | application.inject('model:post', 'config', 'ghost:config');
12 | application.inject('controller', 'config', 'ghost:config');
13 | application.inject('component', 'config', 'ghost:config');
14 | }
15 | };
16 |
17 | export default ConfigInitializer;
18 |
--------------------------------------------------------------------------------
/core/client/tests/helpers/start-app.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | import Application from '../../app';
3 | import Router from '../../router';
4 | import config from '../../config/environment';
5 |
6 | function startApp(attrs) {
7 | var application,
8 | attributes = Ember.merge({}, config.APP);
9 |
10 | attributes = Ember.merge(attributes, attrs); // use defaults, but you can override;
11 |
12 | Ember.run(function () {
13 | application = Application.create(attributes);
14 | application.setupForTesting();
15 | application.injectTestHelpers();
16 | });
17 |
18 | return application;
19 | }
20 |
21 | export default startApp;
22 |
--------------------------------------------------------------------------------
/core/client/app/components/gh-popover-button.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | import DropdownButton from 'ghost/components/gh-dropdown-button';
3 |
4 | var PopoverButton = DropdownButton.extend({
5 | click: Ember.K, // We don't want clicks on popovers, but dropdowns have them. So `K`ill them here.
6 |
7 | mouseEnter: function (event) {
8 | this._super(event);
9 | this.get('dropdown').toggleDropdown(this.get('popoverName'), this);
10 | },
11 |
12 | mouseLeave: function (event) {
13 | this._super(event);
14 | this.get('dropdown').toggleDropdown(this.get('popoverName'), this);
15 | }
16 | });
17 |
18 | export default PopoverButton;
19 |
--------------------------------------------------------------------------------
/core/client/app/helpers/gh-format-timeago.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | var formatTimeago = Ember.HTMLBars.makeBoundHelper(function (arr /* hashParams */) {
3 | if (!arr || !arr.length) {
4 | return;
5 | }
6 |
7 | var timeago = arr[0];
8 | moment.locale('zh-cn'); //hacked by weiping
9 | return moment(timeago).fromNow();
10 | // stefanpenner says cool for small number of timeagos.
11 | // For large numbers moment sucks => single Ember.Object based clock better
12 | // https://github.com/manuelmitasch/ghost-admin-ember-demo/commit/fba3ab0a59238290c85d4fa0d7c6ed1be2a8a82e#commitcomment-5396524
13 | });
14 |
15 | export default formatTimeago;
--------------------------------------------------------------------------------
/core/client/app/templates/reset.hbs:
--------------------------------------------------------------------------------
1 |
12 |
--------------------------------------------------------------------------------
/core/client/app/templates/modals/signin.hbs:
--------------------------------------------------------------------------------
1 | {{#gh-modal-dialog action="closeModal" showClose=true type="action" style="wide" animation="fade"
2 | title="Please re-authenticate" confirm=confirm}}
3 |
4 |
10 |
11 | {{/gh-modal-dialog}}
12 |
--------------------------------------------------------------------------------
/core/client/app/controllers/modals/upload.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 |
3 | var UploadController = Ember.Controller.extend({
4 | acceptEncoding: 'image/*',
5 | actions: {
6 | confirmAccept: function () {
7 | var self = this;
8 |
9 | this.get('model').save().then(function (model) {
10 | self.notifications.showSuccess('保存成功');
11 | return model;
12 | }).catch(function (err) {
13 | self.notifications.showErrors(err);
14 | });
15 | },
16 |
17 | confirmReject: function () {
18 | return false;
19 | }
20 | }
21 | });
22 |
23 | export default UploadController;
24 |
--------------------------------------------------------------------------------
/core/client/app/serializers/application.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | import DS from 'ember-data';
3 | var ApplicationSerializer = DS.RESTSerializer.extend({
4 | serializeIntoHash: function (hash, type, record, options) {
5 | // Our API expects an id on the posted object
6 | options = options || {};
7 | options.includeId = true;
8 |
9 | // We have a plural root in the API
10 | var root = Ember.String.pluralize(type.typeKey),
11 | data = this.serialize(record, options);
12 |
13 | // Don't ever pass uuid's
14 | delete data.uuid;
15 |
16 | hash[root] = [data];
17 | }
18 | });
19 |
20 | export default ApplicationSerializer;
21 |
--------------------------------------------------------------------------------
/core/client/app/validators/reset.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | var ResetValidator = Ember.Object.create({
3 | check: function (model) {
4 | var p1 = model.get('newPassword'),
5 | p2 = model.get('ne2Password'),
6 | validationErrors = [];
7 |
8 | if (!validator.equals(p1, p2)) {
9 | validationErrors.push({
10 | message: '两次输入的新密码不一致。'
11 | });
12 | }
13 |
14 | if (!validator.isLength(p1, 8)) {
15 | validationErrors.push({
16 | message: '新密码至少要8个字符。'
17 | });
18 | }
19 | return validationErrors;
20 | }
21 | });
22 |
23 | export default ResetValidator;
24 |
--------------------------------------------------------------------------------
/core/client/app/serializers/tag.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | import ApplicationSerializer from 'ghost/serializers/application';
3 |
4 | var TagSerializer = ApplicationSerializer.extend({
5 | serializeIntoHash: function (hash, type, record, options) {
6 | options = options || {};
7 | options.includeId = true;
8 |
9 | var root = Ember.String.pluralize(type.typeKey),
10 | data = this.serialize(record, options);
11 |
12 | // Properties that exist on the model but we don't want sent in the payload
13 |
14 | delete data.uuid;
15 | delete data.post_count;
16 |
17 | hash[root] = [data];
18 | }
19 | });
20 |
21 | export default TagSerializer;
22 |
--------------------------------------------------------------------------------
/core/client/app/initializers/trailing-history.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | /*global Ember */
3 |
4 | var trailingHistory,
5 | registerTrailingLocationHistory;
6 |
7 | trailingHistory = Ember.HistoryLocation.extend({
8 | formatURL: function () {
9 | // jscs: disable
10 | return this._super.apply(this, arguments).replace(/\/?$/, '/');
11 | // jscs: enable
12 | }
13 | });
14 |
15 | registerTrailingLocationHistory = {
16 | name: 'registerTrailingLocationHistory',
17 |
18 | initialize: function (container, application) {
19 | application.register('location:trailing-history', trailingHistory);
20 | }
21 | };
22 |
23 | export default registerTrailingLocationHistory;
24 |
--------------------------------------------------------------------------------
/core/client/app/helpers/gh-count-characters.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | var countCharacters = Ember.HTMLBars.makeBoundHelper(function (arr /* hashParams */) {
3 | var el = document.createElement('span'),
4 | length,
5 | content;
6 |
7 | if (!arr || !arr.length) {
8 | return;
9 | }
10 |
11 | content = arr[0] || '';
12 | length = content.length;
13 |
14 | el.className = 'word-count';
15 |
16 | if (length > 180) {
17 | el.style.color = '#E25440';
18 | } else {
19 | el.style.color = '#9E9D95';
20 | }
21 |
22 | el.innerHTML = 200 - length;
23 |
24 | return Ember.String.htmlSafe(el.outerHTML);
25 | });
26 |
27 | export default countCharacters;
28 |
--------------------------------------------------------------------------------
/core/client/app/controllers/settings/code-injection.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | var SettingsCodeInjectionController = Ember.Controller.extend({
3 | actions: {
4 | save: function () {
5 | var self = this;
6 |
7 | return this.get('model').save().then(function (model) {
8 | self.notifications.closePassive();
9 | self.notifications.showSuccess('配置保存成功。');
10 |
11 | return model;
12 | }).catch(function (errors) {
13 | self.notifications.closePassive();
14 | self.notifications.showErrors(errors);
15 | });
16 | }
17 | }
18 | });
19 |
20 | export default SettingsCodeInjectionController;
21 |
--------------------------------------------------------------------------------
/core/client/app/templates/settings/navigation.hbs:
--------------------------------------------------------------------------------
1 |
8 |
9 |
16 |
--------------------------------------------------------------------------------
/core/client/tests/unit/models/role_test.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | import {
3 | describeModel,
4 | it
5 | } from 'ember-mocha';
6 |
7 | describeModel('role', function () {
8 | it('provides a lowercase version of the name', function () {
9 | var model = this.subject({
10 | name: 'Author'
11 | });
12 |
13 | expect(model.get('name')).to.equal('Author');
14 | expect(model.get('lowerCaseName')).to.equal('author');
15 |
16 | Ember.run(function () {
17 | model.set('name', 'Editor');
18 |
19 | expect(model.get('name')).to.equal('Editor');
20 | expect(model.get('lowerCaseName')).to.equal('editor');
21 | });
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/core/client/app/utils/dropdown-service.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | // This is used by the dropdown initializer (and subsequently popovers) to manage closing & toggling
3 | import BodyEventListener from 'ghost/mixins/body-event-listener';
4 |
5 | var DropdownService = Ember.Object.extend(Ember.Evented, BodyEventListener, {
6 | bodyClick: function (event) {
7 | /*jshint unused:false */
8 | this.closeDropdowns();
9 | },
10 | closeDropdowns: function () {
11 | this.trigger('close');
12 | },
13 | toggleDropdown: function (dropdownName, dropdownButton) {
14 | this.trigger('toggle', {target: dropdownName, button: dropdownButton});
15 | }
16 | });
17 |
18 | export default DropdownService;
19 |
--------------------------------------------------------------------------------
/core/client/app/views/posts.js:
--------------------------------------------------------------------------------
1 | import MobileParentView from 'ghost/views/mobile/parent-view';
2 |
3 | var PostsView = MobileParentView.extend({
4 | classNames: ['content-view-container'],
5 | tagName: 'section',
6 |
7 | // Mobile parent view callbacks
8 | showMenu: function () {
9 | $('.js-content-list, .js-content-preview').addClass('show-menu').removeClass('show-content');
10 | },
11 | showContent: function () {
12 | $('.js-content-list, .js-content-preview').addClass('show-content').removeClass('show-menu');
13 | },
14 | showAll: function () {
15 | $('.js-content-list, .js-content-preview').removeClass('show-menu show-content');
16 | }
17 | });
18 |
19 | export default PostsView;
20 |
--------------------------------------------------------------------------------
/core/client/app/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/app/serializers/user.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | import DS from 'ember-data';
3 | import ApplicationSerializer from 'ghost/serializers/application';
4 |
5 | var UserSerializer = ApplicationSerializer.extend(DS.EmbeddedRecordsMixin, {
6 | attrs: {
7 | roles: {embedded: 'always'}
8 | },
9 |
10 | extractSingle: function (store, primaryType, payload) {
11 | var root = this.keyForAttribute(primaryType.typeKey),
12 | pluralizedRoot = Ember.String.pluralize(primaryType.typeKey);
13 |
14 | payload[root] = payload[pluralizedRoot][0];
15 | delete payload[pluralizedRoot];
16 |
17 | return this._super.apply(this, arguments);
18 | }
19 | });
20 |
21 | export default UserSerializer;
22 |
--------------------------------------------------------------------------------
/core/client/app/views/paginated-scroll-box.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | import setScrollClassName from 'ghost/utils/set-scroll-classname';
3 | import PaginationViewMixin from 'ghost/mixins/pagination-view-infinite-scroll';
4 |
5 | var PaginatedScrollBox = Ember.View.extend(PaginationViewMixin, {
6 | attachScrollClassHandler: function () {
7 | var el = this.$();
8 | el.on('scroll', Ember.run.bind(el, setScrollClassName, {
9 | target: el.closest('.content-list'),
10 | offset: 10
11 | }));
12 | }.on('didInsertElement'),
13 |
14 | detachScrollClassHandler: function () {
15 | this.$().off('scroll');
16 | }.on('willDestroyElement')
17 | });
18 |
19 | export default PaginatedScrollBox;
20 |
--------------------------------------------------------------------------------
/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/client/app/routes/signout.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | import AuthenticatedRoute from 'ghost/routes/authenticated';
3 | import styleBody from 'ghost/mixins/style-body';
4 | import loadingIndicator from 'ghost/mixins/loading-indicator';
5 |
6 | var SignoutRoute = AuthenticatedRoute.extend(styleBody, loadingIndicator, {
7 | titleToken: 'Sign Out',
8 |
9 | classNames: ['ghost-signout'],
10 |
11 | afterModel: function (model, transition) {
12 | this.notifications.clear();
13 | if (Ember.canInvoke(transition, 'send')) {
14 | transition.send('invalidateSession');
15 | transition.abort();
16 | } else {
17 | this.send('invalidateSession');
18 | }
19 | }
20 | });
21 |
22 | export default SignoutRoute;
23 |
--------------------------------------------------------------------------------
/core/client/app/mixins/current-user-settings.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | var CurrentUserSettings = Ember.Mixin.create({
3 | transitionAuthor: function () {
4 | var self = this;
5 |
6 | return function (user) {
7 | if (user.get('isAuthor')) {
8 | return self.transitionTo('settings.users.user', user);
9 | }
10 |
11 | return user;
12 | };
13 | },
14 |
15 | transitionEditor: function () {
16 | var self = this;
17 |
18 | return function (user) {
19 | if (user.get('isEditor')) {
20 | return self.transitionTo('settings.users');
21 | }
22 |
23 | return user;
24 | };
25 | }
26 | });
27 |
28 | export default CurrentUserSettings;
29 |
--------------------------------------------------------------------------------
/core/client/app/helpers/gh-count-down-characters.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | var countDownCharacters = Ember.HTMLBars.makeBoundHelper(function (arr /* hashParams */) {
3 | var el = document.createElement('span'),
4 | content,
5 | maxCharacters,
6 | length;
7 |
8 | if (!arr || arr.length < 2) {
9 | return;
10 | }
11 |
12 | content = arr[0] || '';
13 | maxCharacters = arr[1];
14 | length = content.length;
15 |
16 | el.className = 'word-count';
17 |
18 | if (length > maxCharacters) {
19 | el.style.color = '#E25440';
20 | } else {
21 | el.style.color = '#9FBB58';
22 | }
23 |
24 | el.innerHTML = length;
25 |
26 | return Ember.String.htmlSafe(el.outerHTML);
27 | });
28 |
29 | export default countDownCharacters;
30 |
--------------------------------------------------------------------------------
/core/server/helpers/input_password.js:
--------------------------------------------------------------------------------
1 | // # Input Password Helper
2 | // Usage: `{{input_password}}`
3 | //
4 | // Password input used on private.hbs for password-protected blogs
5 | //
6 | // We use the name meta_title to match the helper for consistency:
7 | // jscs:disable requireCamelCaseOrUpperCaseIdentifiers
8 |
9 | var hbs = require('express-hbs'),
10 | utils = require('./utils'),
11 | input_password;
12 |
13 | input_password = function () {
14 | var output = utils.inputTemplate({
15 | type: 'text',
16 | name: 'password',
17 | className: 'private-login-password',
18 | extras: 'autofocus="autofocus" placeholder="请输入暗号"'
19 | });
20 | return new hbs.handlebars.SafeString(output);
21 | };
22 |
23 | module.exports = input_password;
24 |
--------------------------------------------------------------------------------
/core/client/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "predef": [
3 | "document",
4 | "window",
5 | "-Promise",
6 | "-Notification",
7 | "$",
8 | "validator",
9 | "ic",
10 | "SimpleAuth",
11 | "NProgress",
12 | "moment"
13 | ],
14 | "browser": true,
15 | "boss": true,
16 | "curly": true,
17 | "debug": false,
18 | "devel": true,
19 | "eqeqeq": true,
20 | "evil": true,
21 | "forin": false,
22 | "immed": false,
23 | "laxbreak": false,
24 | "newcap": true,
25 | "noarg": true,
26 | "noempty": false,
27 | "nonew": false,
28 | "nomen": false,
29 | "onevar": false,
30 | "plusplus": false,
31 | "regexp": false,
32 | "undef": true,
33 | "sub": true,
34 | "strict": false,
35 | "white": false,
36 | "eqnull": true,
37 | "esnext": true,
38 | "unused": true
39 | }
40 |
--------------------------------------------------------------------------------
/core/client/app/mixins/loading-indicator.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | // mixin used for routes to display a loading indicator when there is network activity
3 | var loaderOptions,
4 | loadingIndicator;
5 |
6 | loaderOptions = {
7 | showSpinner: false
8 | };
9 |
10 | NProgress.configure(loaderOptions);
11 |
12 | loadingIndicator = Ember.Mixin.create({
13 | actions: {
14 |
15 | loading: function () {
16 | NProgress.start();
17 | this.router.one('didTransition', function () {
18 | NProgress.done();
19 | });
20 |
21 | return true;
22 | },
23 |
24 | error: function () {
25 | NProgress.done();
26 |
27 | return true;
28 | }
29 | }
30 | });
31 |
32 | export default loadingIndicator;
33 |
--------------------------------------------------------------------------------
/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 | it('should not start a connect server when required', function (done) {
13 | request(url, function (error, response, body) {
14 | should(response).equal(undefined);
15 | should(body).equal(undefined);
16 | should(error).not.equal(undefined);
17 | should(error.code).equal('ECONNREFUSED');
18 | done();
19 | });
20 | });
21 | });
22 |
--------------------------------------------------------------------------------
/core/client/app/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/app/controllers/editor/new.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | import EditorControllerMixin from 'ghost/mixins/editor-base-controller';
3 |
4 | var EditorNewController = Ember.Controller.extend(EditorControllerMixin, {
5 | // Overriding autoSave on the base controller, as the new controller shouldn't be autosaving
6 | autoSave: Ember.K,
7 | actions: {
8 | /**
9 | * Redirect to editor after the first save
10 | */
11 | save: function (options) {
12 | var self = this;
13 | return this._super(options).then(function (model) {
14 | if (model.get('id')) {
15 | self.replaceRoute('editor.edit', model);
16 | }
17 | });
18 | }
19 | }
20 | });
21 |
22 | export default EditorNewController;
23 |
--------------------------------------------------------------------------------
/core/client/app/mixins/text-input.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | var BlurField = Ember.Mixin.create({
3 | selectOnClick: false,
4 | stopEnterKeyDownPropagation: false,
5 |
6 | click: function (event) {
7 | if (this.get('selectOnClick')) {
8 | event.currentTarget.select();
9 | }
10 | },
11 |
12 | keyDown: function (event) {
13 | // stop event propagation when pressing "enter"
14 | // most useful in the case when undesired (global) keyboard shortcuts are getting triggered while interacting
15 | // with this particular input element.
16 | if (this.get('stopEnterKeyDownPropagation') && event.keyCode === 13) {
17 | event.stopPropagation();
18 |
19 | return true;
20 | }
21 | }
22 | });
23 |
24 | export default BlurField;
25 |
--------------------------------------------------------------------------------
/core/client/app/routes/settings/apps.js:
--------------------------------------------------------------------------------
1 | import AuthenticatedRoute from 'ghost/routes/authenticated';
2 | import CurrentUserSettings from 'ghost/mixins/current-user-settings';
3 | import styleBody from 'ghost/mixins/style-body';
4 |
5 | var AppsRoute = AuthenticatedRoute.extend(styleBody, CurrentUserSettings, {
6 | titleToken: 'Apps',
7 |
8 | classNames: ['settings-view-apps'],
9 |
10 | beforeModel: function () {
11 | if (!this.get('config.apps')) {
12 | return this.transitionTo('settings.general');
13 | }
14 |
15 | return this.get('session.user')
16 | .then(this.transitionAuthor())
17 | .then(this.transitionEditor());
18 | },
19 |
20 | model: function () {
21 | return this.store.find('app');
22 | }
23 | });
24 |
25 | export default AppsRoute;
26 |
--------------------------------------------------------------------------------
/core/client/app/components/gh-file-upload.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | var FileUpload = Ember.Component.extend({
3 | _file: null,
4 |
5 | uploadButtonText: 'Text',
6 |
7 | uploadButtonDisabled: true,
8 |
9 | change: function (event) {
10 | this.set('uploadButtonDisabled', false);
11 | this.sendAction('onAdd');
12 | this._file = event.target.files[0];
13 | },
14 |
15 | onUpload: 'onUpload',
16 |
17 | actions: {
18 | upload: function () {
19 | if (!this.uploadButtonDisabled && this._file) {
20 | this.sendAction('onUpload', this._file);
21 | }
22 |
23 | // Prevent double post by disabling the button.
24 | this.set('uploadButtonDisabled', true);
25 | }
26 | }
27 | });
28 |
29 | export default FileUpload;
30 |
--------------------------------------------------------------------------------
/core/client/app/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/app/models/tag.js:
--------------------------------------------------------------------------------
1 | import DS from 'ember-data';
2 | import ValidationEngine from 'ghost/mixins/validation-engine';
3 | import NProgressSaveMixin from 'ghost/mixins/nprogress-save';
4 |
5 | var Tag = DS.Model.extend(NProgressSaveMixin, ValidationEngine, {
6 | validationType: 'tag',
7 |
8 | uuid: DS.attr('string'),
9 | name: DS.attr('string'),
10 | slug: DS.attr('string'),
11 | description: DS.attr('string'),
12 | parent: DS.attr(),
13 | meta_title: DS.attr('string'),
14 | meta_description: DS.attr('string'),
15 | image: DS.attr('string'),
16 | hidden: DS.attr('boolean'),
17 | created_at: DS.attr('moment-date'),
18 | updated_at: DS.attr('moment-date'),
19 | created_by: DS.attr(),
20 | updated_by: DS.attr(),
21 | post_count: DS.attr('number')
22 | });
23 |
24 | export default Tag;
25 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "0.10"
4 | - "0.12"
5 | - "iojs-v1.2.0"
6 | sudo: false
7 | cache:
8 | directories:
9 | - node_modules
10 | - bower_components
11 | addons:
12 | postgresql: "9.3"
13 | env:
14 | global:
15 | - GITHUB_OAUTH_KEY=003a44d58f12089d0c0261338298af3813330949
16 | matrix:
17 | - DB=sqlite3 NODE_ENV=testing
18 | - DB=mysql NODE_ENV=testing-mysql
19 | - DB=pg NODE_ENV=testing-pg
20 | before_install:
21 | - git clone git://github.com/n1k0/casperjs.git ~/casperjs
22 | - cd ~/casperjs
23 | - git checkout tags/1.1-beta3
24 | - export PATH=$PATH:`pwd`/bin
25 | - cd -
26 | - if [ $DB == "mysql" ]; then mysql -e 'create database ghost_testing'; fi
27 | - if [ $DB == "pg" ]; then psql -c 'create database ghost_testing;' -U postgres; fi
28 | before_script:
29 | - phantomjs --version
30 | - casperjs --version
31 |
--------------------------------------------------------------------------------
/core/client/app/validators/new-user.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | var NewUserValidator = Ember.Object.extend({
3 | check: function (model) {
4 | var data = model.getProperties('name', 'email', 'password'),
5 | validationErrors = [];
6 |
7 | if (!validator.isLength(data.name, 1)) {
8 | validationErrors.push({
9 | message: '请填写姓名/昵称。'
10 | });
11 | }
12 |
13 | if (!validator.isEmail(data.email)) {
14 | validationErrors.push({
15 | message: '邮箱格式不正确。'
16 | });
17 | }
18 |
19 | if (!validator.isLength(data.password, 8)) {
20 | validationErrors.push({
21 | message: '密码至少要8个字符。'
22 | });
23 | }
24 |
25 | return validationErrors;
26 | }
27 | });
28 |
29 | export default NewUserValidator;
30 |
--------------------------------------------------------------------------------
/core/client/app/routes/settings/labs.js:
--------------------------------------------------------------------------------
1 | import AuthenticatedRoute from 'ghost/routes/authenticated';
2 | import styleBody from 'ghost/mixins/style-body';
3 | import CurrentUserSettings from 'ghost/mixins/current-user-settings';
4 | import loadingIndicator from 'ghost/mixins/loading-indicator';
5 |
6 | var LabsRoute = AuthenticatedRoute.extend(styleBody, loadingIndicator, CurrentUserSettings, {
7 | titleToken: 'Labs',
8 |
9 | classNames: ['settings'],
10 | beforeModel: function () {
11 | return this.get('session.user')
12 | .then(this.transitionAuthor())
13 | .then(this.transitionEditor());
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 | export default LabsRoute;
24 |
--------------------------------------------------------------------------------
/core/client/app/views/content-preview-content-view.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | import setScrollClassName from 'ghost/utils/set-scroll-classname';
3 |
4 | var PostContentView = Ember.View.extend({
5 | classNames: ['content-preview-content'],
6 |
7 | didInsertElement: function () {
8 | var el = this.$();
9 | el.on('scroll', Ember.run.bind(el, setScrollClassName, {
10 | target: el.closest('.content-preview'),
11 | offset: 10
12 | }));
13 | },
14 |
15 | contentObserver: function () {
16 | var el = this.$();
17 |
18 | if (el) {
19 | el.closest('.content-preview').scrollTop(0);
20 | }
21 | }.observes('controller.content'),
22 |
23 | willDestroyElement: function () {
24 | var el = this.$();
25 | el.off('scroll');
26 | }
27 | });
28 |
29 | export default PostContentView;
30 |
--------------------------------------------------------------------------------
/core/client/app/models/slug-generator.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | var SlugGenerator = Ember.Object.extend({
3 | ghostPaths: null,
4 | slugType: null,
5 | value: null,
6 | toString: function () {
7 | return this.get('value');
8 | },
9 | generateSlug: function (textToSlugify) {
10 | var self = this,
11 | url;
12 |
13 | if (!textToSlugify) {
14 | return Ember.RSVP.resolve('');
15 | }
16 |
17 | url = this.get('ghostPaths.url').api('slugs', this.get('slugType'), encodeURIComponent(textToSlugify));
18 |
19 | return ic.ajax.request(url, {
20 | type: 'GET'
21 | }).then(function (response) {
22 | var slug = response.slugs[0].slug;
23 | self.set('value', slug);
24 | return slug;
25 | });
26 | }
27 | });
28 |
29 | export default SlugGenerator;
30 |
--------------------------------------------------------------------------------
/core/client/app/mixins/settings-menu-controller.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | var SettingsMenuControllerMixin = Ember.Mixin.create({
3 | needs: 'application',
4 |
5 | isViewingSubview: Ember.computed('controllers.application.showSettingsMenu', function (key, value) {
6 | // Not viewing a subview if we can't even see the PSM
7 | if (!this.get('controllers.application.showSettingsMenu')) {
8 | return false;
9 | }
10 | if (arguments.length > 1) {
11 | return value;
12 | }
13 |
14 | return false;
15 | }),
16 |
17 | actions: {
18 | showSubview: function () {
19 | this.set('isViewingSubview', true);
20 | },
21 |
22 | closeSubview: function () {
23 | this.set('isViewingSubview', false);
24 | }
25 | }
26 | });
27 |
28 | export default SettingsMenuControllerMixin;
29 |
--------------------------------------------------------------------------------
/core/client/app/templates/settings/tags.hbs:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 | {{#each tag in tags}}
11 |
12 |
18 |
19 | {{/each}}
20 |
21 |
--------------------------------------------------------------------------------
/core/client/app/components/gh-trim-focus-input.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | /*global device*/
3 | var TrimFocusInput = Ember.TextField.extend({
4 | focus: true,
5 |
6 | attributeBindings: ['autofocus'],
7 |
8 | autofocus: Ember.computed(function () {
9 | if (this.get('focus')) {
10 | return (device.ios()) ? false : 'autofocus';
11 | }
12 |
13 | return false;
14 | }),
15 |
16 | didInsertElement: function () {
17 | // This fix is required until Mobile Safari has reliable
18 | // autofocus, select() or focus() support
19 | if (this.get('focus') && !device.ios()) {
20 | this.$().val(this.$().val()).focus();
21 | }
22 | },
23 |
24 | focusOut: function () {
25 | var text = this.$().val();
26 |
27 | this.$().val(text.trim());
28 | }
29 | });
30 |
31 | export default TrimFocusInput;
32 |
--------------------------------------------------------------------------------
/core/client/app/utils/bound-one-way.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | /**
3 | * Defines a property similarly to `Ember.computed.oneway`,
4 | * save that while a `oneway` loses its binding upon being set,
5 | * the `BoundOneWay` will continue to listen for upstream changes.
6 | *
7 | * This is an ideal tool for working with values inside of {{input}}
8 | * elements.
9 | * @param {*} upstream
10 | * @param {function} transform a function to transform the **upstream** value.
11 | */
12 | var BoundOneWay = function (upstream, transform) {
13 | if (typeof transform !== 'function') {
14 | // default to the identity function
15 | transform = function (value) { return value; };
16 | }
17 |
18 | return Ember.computed(upstream, function (key, value) {
19 | return arguments.length > 1 ? value : transform(this.get(upstream));
20 | });
21 | };
22 |
23 | export default BoundOneWay;
24 |
--------------------------------------------------------------------------------
/core/client/app/controllers/feature.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | var FeatureController = Ember.Controller.extend(Ember.PromiseProxyMixin, {
3 | init: function () {
4 | var promise;
5 |
6 | promise = this.store.find('setting', {type: 'blog,theme'}).then(function (settings) {
7 | return settings.get('firstObject');
8 | });
9 |
10 | this.set('promise', promise);
11 | },
12 |
13 | setting: Ember.computed.alias('content'),
14 |
15 | labs: Ember.computed('isSettled', 'setting.labs', function () {
16 | var value = {};
17 |
18 | if (this.get('isFulfilled')) {
19 | try {
20 | value = JSON.parse(this.get('setting.labs') || {});
21 | } catch (err) {
22 | value = {};
23 | }
24 | }
25 |
26 | return value;
27 | })
28 | });
29 |
30 | export default FeatureController;
31 |
--------------------------------------------------------------------------------
/core/client/app/validators/post.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | var PostValidator = Ember.Object.create({
3 | check: function (model) {
4 | var validationErrors = [],
5 | data = model.getProperties('title', 'meta_title', 'meta_description');
6 |
7 | if (validator.empty(data.title)) {
8 | validationErrors.push({
9 | message: '博文标题不能为空。'
10 | });
11 | }
12 |
13 | if (!validator.isLength(data.meta_title, 0, 150)) {
14 | validationErrors.push({
15 | message: '呈现标题不能超过150个字。'
16 | });
17 | }
18 |
19 | if (!validator.isLength(data.meta_description, 0, 200)) {
20 | validationErrors.push({
21 | message: '呈现摘要不能超过200个字。'
22 | });
23 | }
24 |
25 | return validationErrors;
26 | }
27 | });
28 |
29 | export default PostValidator;
30 |
--------------------------------------------------------------------------------
/core/client/app/components/gh-role-selector.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | import GhostSelect from 'ghost/components/gh-select';
3 |
4 | var RolesSelector = GhostSelect.extend({
5 | roles: Ember.computed.alias('options'),
6 |
7 | options: Ember.computed(function () {
8 | var rolesPromise = this.store.find('role', {permissions: 'assign'});
9 |
10 | //汉化修改后台角色呈现名称
11 | this.store.find('role', { permissions: 'assign' }).then(function(item){
12 | item.forEach( function( i ){
13 | var n = i.get('description');
14 | i.set('name', n);
15 | });
16 | });
17 | var rolesPromise = this.store.find('role', { permissions: 'assign' });
18 |
19 |
20 | return Ember.ArrayProxy.extend(Ember.PromiseProxyMixin)
21 | .create({promise: rolesPromise});
22 | })
23 | });
24 |
25 | export default RolesSelector;
26 |
--------------------------------------------------------------------------------
/core/client/app/components/gh-navitem.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | var NavItemComponent = Ember.Component.extend({
3 | classNames: 'navigation-item',
4 |
5 | attributeBindings: ['order:data-order'],
6 | order: Ember.computed.readOnly('navItem.order'),
7 |
8 | keyPress: function (event) {
9 | // enter key
10 | if (event.keyCode === 13) {
11 | event.preventDefault();
12 | this.get('controller').send('addItem');
13 | }
14 | },
15 |
16 | actions: {
17 | addItem: function () {
18 | this.sendAction('addItem');
19 | },
20 |
21 | deleteItem: function (item) {
22 | this.sendAction('deleteItem', item);
23 | },
24 |
25 | updateUrl: function (value) {
26 | this.sendAction('updateUrl', value, this.get('navItem'));
27 | }
28 | }
29 | });
30 |
31 | export default NavItemComponent;
32 |
--------------------------------------------------------------------------------
/core/server/storage/index.js:
--------------------------------------------------------------------------------
1 | var errors = require('../errors'),
2 | config = require('../config'),
3 | storage = {};
4 |
5 | function getStorage(storageChoice) {
6 | var storagePath,
7 | storageConfig;
8 |
9 | storageChoice = config.storage.active;
10 | storagePath = config.paths.storage;
11 | storageConfig = config.storage[storageChoice];
12 |
13 | if (storage[storageChoice]) {
14 | return storage[storageChoice];
15 | }
16 |
17 | try {
18 | // TODO: determine if storage has all the necessary methods.
19 | storage[storageChoice] = require(storagePath);
20 | } catch (e) {
21 | errors.logError(e);
22 | }
23 |
24 | // Instantiate and cache the storage module instance.
25 | storage[storageChoice] = new storage[storageChoice](storageConfig);
26 |
27 | return storage[storageChoice];
28 | }
29 |
30 | module.exports.getStorage = getStorage;
31 |
--------------------------------------------------------------------------------
/core/client/app/views/settings.js:
--------------------------------------------------------------------------------
1 | import MobileParentView from 'ghost/views/mobile/parent-view';
2 |
3 | var SettingsView = MobileParentView.extend({
4 | // MobileParentView callbacks
5 | showMenu: function () {
6 | $('.js-settings-header-inner').css('display', 'none');
7 | $('.js-settings-menu').css({right: '0', left: '0', 'margin-right': '0'});
8 | $('.js-settings-content').css({right: '-100%', left: '100%', 'margin-left': '15'});
9 | },
10 | showContent: function () {
11 | $('.js-settings-menu').css({right: '100%', left: '-110%', 'margin-right': '15px'});
12 | $('.js-settings-content').css({right: '0', left: '0', 'margin-left': '0'});
13 | $('.js-settings-header-inner').css('display', 'block');
14 | },
15 | showAll: function () {
16 | $('.js-settings-menu, .js-settings-content').removeAttr('style');
17 | }
18 | });
19 |
20 | export default SettingsView;
21 |
--------------------------------------------------------------------------------
/core/client/app/templates/modals/invite-new-user.hbs:
--------------------------------------------------------------------------------
1 |
2 | {{#gh-modal-dialog action="closeModal" showClose=true type="action"
3 | title="邀请用户" confirm=confirm class="invite-new-user"}}
4 |
5 |
21 |
22 | {{/gh-modal-dialog}}
23 |
--------------------------------------------------------------------------------
/core/client/app/routes/reset.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | import styleBody from 'ghost/mixins/style-body';
3 | import loadingIndicator from 'ghost/mixins/loading-indicator';
4 |
5 | var ResetRoute = Ember.Route.extend(styleBody, loadingIndicator, {
6 | classNames: ['ghost-reset'],
7 |
8 | beforeModel: function () {
9 | if (this.get('session').isAuthenticated) {
10 | this.notifications.showWarn('You can\'t reset your password while you\'re signed in.', {delayed: true});
11 | this.transitionTo(SimpleAuth.Configuration.routeAfterAuthentication);
12 | }
13 | },
14 |
15 | setupController: function (controller, params) {
16 | controller.token = params.token;
17 | },
18 |
19 | // Clear out any sensitive information
20 | deactivate: function () {
21 | this._super();
22 | this.controller.clearData();
23 | }
24 | });
25 |
26 | export default ResetRoute;
27 |
--------------------------------------------------------------------------------
/core/server/helpers/is.js:
--------------------------------------------------------------------------------
1 | // # Is Helper
2 | // Usage: `{{#is "paged"}}`, `{{#is "index, paged"}}`
3 | // Checks whether we're in a given context.
4 | var _ = require('lodash'),
5 | errors = require('../errors'),
6 | is;
7 |
8 | is = function (context, options) {
9 | options = options || {};
10 |
11 | var currentContext = options.data.root.context;
12 |
13 | if (!_.isString(context)) {
14 | errors.logWarn('Invalid or no attribute given to is helper');
15 | return;
16 | }
17 |
18 | function evaluateContext(expr) {
19 | return expr.split(',').map(function (v) {
20 | return v.trim();
21 | }).reduce(function (p, c) {
22 | return p || _.contains(currentContext, c);
23 | }, false);
24 | }
25 |
26 | if (evaluateContext(context)) {
27 | return options.fn(this);
28 | }
29 | return options.inverse(this);
30 | };
31 |
32 | module.exports = is;
33 |
--------------------------------------------------------------------------------
/core/test/unit/server_helpers/encode_spec.js:
--------------------------------------------------------------------------------
1 | /*globals describe, before, it*/
2 | /*jshint expr:true*/
3 | var should = require('should'),
4 | hbs = require('express-hbs'),
5 | utils = require('./utils'),
6 |
7 | // Stuff we are testing
8 | handlebars = hbs.handlebars,
9 | helpers = require('../../../server/helpers');
10 |
11 | describe('{{encode}} helper', function () {
12 | before(function () {
13 | utils.loadHelpers();
14 | });
15 |
16 | it('has loaded encode helper', function () {
17 | should.exist(handlebars.helpers.encode);
18 | });
19 |
20 | it('can escape URI', function () {
21 | var uri = '$pecial!Charact3r(De[iver]y)Foo #Bar',
22 | expected = '%24pecial!Charact3r(De%5Biver%5Dy)Foo%20%23Bar',
23 | escaped = helpers.encode(uri);
24 |
25 | should.exist(escaped);
26 | String(escaped).should.equal(expected);
27 | });
28 | });
29 |
--------------------------------------------------------------------------------
/core/client/app/components/gh-notifications.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | var NotificationsComponent = Ember.Component.extend({
3 | tagName: 'aside',
4 | classNames: 'notifications',
5 | classNameBindings: ['location'],
6 |
7 | messages: Ember.computed.filter('notifications', function (notification) {
8 | // If this instance of the notifications component has no location affinity
9 | // then it gets all notifications
10 | if (!this.get('location')) {
11 | return true;
12 | }
13 |
14 | var displayLocation = (typeof notification.toJSON === 'function') ?
15 | notification.get('location') : notification.location;
16 |
17 | return this.get('location') === displayLocation;
18 | }),
19 |
20 | messageCountObserver: function () {
21 | this.sendAction('notify', this.get('messages').length);
22 | }.observes('messages.[]')
23 | });
24 |
25 | export default NotificationsComponent;
26 |
--------------------------------------------------------------------------------
/core/client/app/mixins/pagination-route.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | var defaultPaginationSettings,
3 | PaginationRoute;
4 |
5 | defaultPaginationSettings = {
6 | page: 1,
7 | limit: 15
8 | };
9 |
10 | PaginationRoute = Ember.Mixin.create({
11 | /**
12 | * Sets up pagination details
13 | * @param {object} settings specifies additional pagination details
14 | */
15 | setupPagination: function (settings) {
16 | settings = settings || {};
17 | for (var key in defaultPaginationSettings) {
18 | if (defaultPaginationSettings.hasOwnProperty(key)) {
19 | if (!settings.hasOwnProperty(key)) {
20 | settings[key] = defaultPaginationSettings[key];
21 | }
22 | }
23 | }
24 |
25 | this.set('paginationSettings', settings);
26 | this.controller.set('paginationSettings', settings);
27 | }
28 | });
29 |
30 | export default PaginationRoute;
31 |
--------------------------------------------------------------------------------
/core/client/app/models/setting.js:
--------------------------------------------------------------------------------
1 | import DS from 'ember-data';
2 | import ValidationEngine from 'ghost/mixins/validation-engine';
3 | import NProgressSaveMixin from 'ghost/mixins/nprogress-save';
4 |
5 | var Setting = DS.Model.extend(NProgressSaveMixin, ValidationEngine, {
6 | validationType: 'setting',
7 |
8 | title: DS.attr('string'),
9 | description: DS.attr('string'),
10 | email: DS.attr('string'),
11 | logo: DS.attr('string'),
12 | cover: DS.attr('string'),
13 | defaultLang: DS.attr('string'),
14 | postsPerPage: DS.attr('number'),
15 | forceI18n: DS.attr('boolean'),
16 | permalinks: DS.attr('string'),
17 | activeTheme: DS.attr('string'),
18 | availableThemes: DS.attr(),
19 | ghost_head: DS.attr('string'),
20 | ghost_foot: DS.attr('string'),
21 | labs: DS.attr('string'),
22 | navigation: DS.attr('string'),
23 | isPrivate: DS.attr('boolean'),
24 | password: DS.attr('string')
25 | });
26 |
27 | export default Setting;
28 |
--------------------------------------------------------------------------------
/core/client/app/mixins/style-body.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | // mixin used for routes that need to set a css className on the body tag
3 |
4 | var styleBody = Ember.Mixin.create({
5 | activate: function () {
6 | this._super();
7 |
8 | var cssClasses = this.get('classNames');
9 |
10 | if (cssClasses) {
11 | Ember.run.schedule('afterRender', null, function () {
12 | cssClasses.forEach(function (curClass) {
13 | Ember.$('body').addClass(curClass);
14 | });
15 | });
16 | }
17 | },
18 |
19 | deactivate: function () {
20 | this._super();
21 |
22 | var cssClasses = this.get('classNames');
23 |
24 | Ember.run.schedule('afterRender', null, function () {
25 | cssClasses.forEach(function (curClass) {
26 | Ember.$('body').removeClass(curClass);
27 | });
28 | });
29 | }
30 | });
31 |
32 | export default styleBody;
33 |
--------------------------------------------------------------------------------
/core/test/unit/server_helpers/input_password_spec.js:
--------------------------------------------------------------------------------
1 | /*globals describe, before, it*/
2 | /*jshint expr:true*/
3 | var should = require('should'),
4 | hbs = require('express-hbs'),
5 | utils = require('./utils'),
6 |
7 | // Stuff we are testing
8 | handlebars = hbs.handlebars,
9 | helpers = require('../../../server/helpers');
10 |
11 | describe('{{input_password}} helper', function () {
12 | before(function () {
13 | utils.loadHelpers();
14 | });
15 |
16 | it('has loaded input_password helper', function () {
17 | should.exist(handlebars.helpers.input_password);
18 | });
19 |
20 | it('returns the correct input', function () {
21 | var markup = '',
22 | rendered = helpers.input_password();
23 | should.exist(rendered);
24 |
25 | String(rendered).should.equal(markup);
26 | });
27 | });
28 |
--------------------------------------------------------------------------------
/core/client/app/components/gh-url-preview.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | /*
3 | Example usage:
4 | {{gh-url-preview prefix="tag" slug=theSlugValue tagName="p" classNames="description"}}
5 | */
6 | var urlPreview = Ember.Component.extend({
7 | classNames: 'ghost-url-preview',
8 | prefix: null,
9 | slug: null,
10 |
11 | url: Ember.computed('slug', function () {
12 | // Get the blog URL and strip the scheme
13 | var blogUrl = this.get('config').blogUrl,
14 | noSchemeBlogUrl = blogUrl.substr(blogUrl.indexOf('://') + 3), // Remove `http[s]://`
15 |
16 | // Get the prefix and slug values
17 | prefix = this.get('prefix') ? this.get('prefix') + '/' : '',
18 | slug = this.get('slug') ? this.get('slug') + '/' : '',
19 |
20 | // Join parts of the URL together with slashes
21 | theUrl = noSchemeBlogUrl + '/' + prefix + slug;
22 |
23 | return theUrl;
24 | })
25 | });
26 |
27 | export default urlPreview;
28 |
--------------------------------------------------------------------------------
/core/client/app/controllers/settings/users/index.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | import PaginationControllerMixin from 'ghost/mixins/pagination-controller';
3 |
4 | var UsersIndexController = Ember.ArrayController.extend(PaginationControllerMixin, {
5 | init: function () {
6 | // let the PaginationControllerMixin know what type of model we will be paginating
7 | // this is necessary because we do not have access to the model inside the Controller::init method
8 | this._super({modelType: 'user'});
9 | },
10 |
11 | users: Ember.computed.alias('model'),
12 |
13 | activeUsers: Ember.computed.filter('users', function (user) {
14 | return /^active|warn-[1-4]|locked$/.test(user.get('status'));
15 | }),
16 |
17 | invitedUsers: Ember.computed.filter('users', function (user) {
18 | var status = user.get('status');
19 |
20 | return status === 'invited' || status === 'invited-pending';
21 | })
22 | });
23 |
24 | export default UsersIndexController;
25 |
--------------------------------------------------------------------------------
/core/client/app/components/gh-tab-pane.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | // See gh-tabs-manager.js for use
3 | var TabPane = Ember.Component.extend({
4 | classNameBindings: ['active'],
5 |
6 | tabsManager: Ember.computed(function () {
7 | return this.nearestWithProperty('isTabsManager');
8 | }),
9 |
10 | tab: Ember.computed('tabsManager.tabs.[]', 'tabsManager.tabPanes.[]', function () {
11 | var index = this.get('tabsManager.tabPanes').indexOf(this),
12 | tabs = this.get('tabsManager.tabs');
13 |
14 | return tabs && tabs.objectAt(index);
15 | }),
16 |
17 | active: Ember.computed.alias('tab.active'),
18 |
19 | // Register with the tabs manager
20 | registerWithTabs: function () {
21 | this.get('tabsManager').registerTabPane(this);
22 | }.on('didInsertElement'),
23 |
24 | unregisterWithTabs: function () {
25 | this.get('tabsManager').unregisterTabPane(this);
26 | }.on('willDestroyElement')
27 | });
28 |
29 | export default TabPane;
30 |
--------------------------------------------------------------------------------
/core/client/app/templates/components/gh-navitem.hbs:
--------------------------------------------------------------------------------
1 | {{#unless navItem.last}}
2 |
3 | 排序
4 |
5 | {{/unless}}
6 |
7 |
8 | {{gh-trim-focus-input focus=navItem.last placeholder="名称" value=navItem.label}}
9 |
10 |
11 | {{gh-navitem-url-input baseUrl=baseUrl url=navItem.url last=navItem.last change="updateUrl"}}
12 |
13 |
14 |
15 | {{#if navItem.last}}
16 |
19 | {{else}}
20 |
23 | {{/if}}
24 |
25 |
--------------------------------------------------------------------------------
/core/client/app/components/gh-tab.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | // See gh-tabs-manager.js for use
3 | var Tab = Ember.Component.extend({
4 | tabsManager: Ember.computed(function () {
5 | return this.nearestWithProperty('isTabsManager');
6 | }),
7 |
8 | active: Ember.computed('tabsManager.activeTab', function () {
9 | return this.get('tabsManager.activeTab') === this;
10 | }),
11 |
12 | index: Ember.computed('tabsManager.tabs.@each', function () {
13 | return this.get('tabsManager.tabs').indexOf(this);
14 | }),
15 |
16 | // Select on click
17 | click: function () {
18 | this.get('tabsManager').select(this);
19 | },
20 |
21 | // Registration methods
22 | registerWithTabs: function () {
23 | this.get('tabsManager').registerTab(this);
24 | }.on('didInsertElement'),
25 |
26 | unregisterWithTabs: function () {
27 | this.get('tabsManager').unregisterTab(this);
28 | }.on('willDestroyElement')
29 | });
30 |
31 | export default Tab;
32 |
--------------------------------------------------------------------------------
/core/test/unit/server_helpers/utils.js:
--------------------------------------------------------------------------------
1 | // # Helper Test Utils
2 | //
3 | // Contains shared code for intialising tests
4 | //
5 | // @TODO refactor this file out of existence
6 | // I believe if we refactor the handlebars instances and helpers to be more self-contained and modular
7 | // We can likely have init functions which replace the need for this file
8 |
9 | var hbs = require('express-hbs'),
10 | _ = require('lodash'),
11 |
12 | // Stuff we are testing
13 | helpers = require('../../../server/helpers'),
14 | config = require('../../../server/config'),
15 | origConfig = _.cloneDeep(config.get()),
16 | utils = {};
17 |
18 | utils.loadHelpers = function () {
19 | var adminHbs = hbs.create();
20 | helpers.loadCoreHelpers(adminHbs);
21 | };
22 |
23 | utils.overrideConfig = function (newConfig) {
24 | config.set(newConfig);
25 | };
26 |
27 | utils.restoreConfig = function () {
28 | config.set(origConfig);
29 | };
30 |
31 | module.exports = utils;
32 | module.exports.config = config;
33 |
--------------------------------------------------------------------------------
/core/client/app/validators/tag-settings.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | var TagSettingsValidator = Ember.Object.create({
3 | check: function (model) {
4 | var validationErrors = [],
5 | data = model.getProperties('name', 'meta_title', 'meta_description');
6 |
7 | if (validator.empty(data.name)) {
8 | validationErrors.push({
9 | message: 'You must specify a name for the tag.'
10 | });
11 | }
12 |
13 | if (!validator.isLength(data.meta_title, 0, 150)) {
14 | validationErrors.push({
15 | message: 'Meta Title cannot be longer than 150 characters.'
16 | });
17 | }
18 |
19 | if (!validator.isLength(data.meta_description, 0, 200)) {
20 | validationErrors.push({
21 | message: 'Meta Description cannot be longer than 200 characters.'
22 | });
23 | }
24 |
25 | return validationErrors;
26 | }
27 | });
28 |
29 | export default TagSettingsValidator;
30 |
--------------------------------------------------------------------------------
/core/client/app/views/settings/tags/settings-menu.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | var TagsSettingsMenuView = Ember.View.extend({
3 | saveText: Ember.computed('controller.model.isNew', function () {
4 | return this.get('controller.model.isNew') ?
5 | 'Add Tag' :
6 | 'Save Tag';
7 | }),
8 |
9 | // This observer loads and resets the uploader whenever the active tag changes,
10 | // ensuring that we can reuse the whole settings menu.
11 | updateUploader: Ember.observer('controller.activeTag.image', 'controller.uploaderReference', function () {
12 | var uploader = this.get('controller.uploaderReference'),
13 | image = this.get('controller.activeTag.image');
14 |
15 | if (uploader && uploader[0]) {
16 | if (image) {
17 | uploader[0].uploaderUi.initWithImage();
18 | } else {
19 | uploader[0].uploaderUi.reset();
20 | }
21 | }
22 | })
23 | });
24 |
25 | export default TagsSettingsMenuView;
26 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | // # Ghost bootloader
2 | // Orchestrates the loading of Ghost
3 | // When run from command line.
4 |
5 | var express,
6 | ghost,
7 | parentApp,
8 | errors;
9 |
10 | // process.env.NODE_ENV = process.env.NODE_ENV || 'production'; // 4 点云
11 |
12 | // Make sure dependencies are installed and file system permissions are correct.
13 | require('./core/server/utils/startup-check').check();
14 |
15 | // Proceed with startup
16 | express = require('express');
17 | ghost = require('./core');
18 | errors = require('./core/server/errors');
19 |
20 | // Create our parent express app instance.
21 | parentApp = express();
22 |
23 | ghost().then(function (ghostServer) {
24 | // Mount our ghost instance on our desired subdirectory path if it exists.
25 | parentApp.use(ghostServer.config.paths.subdir, ghostServer.rootApp);
26 |
27 | // Let ghost handle starting our server instance.
28 | ghostServer.start(parentApp);
29 | }).catch(function (err) {
30 | errors.logErrorAndExit(err, err.context, err.help);
31 | });
32 |
--------------------------------------------------------------------------------
/core/server/helpers/url.js:
--------------------------------------------------------------------------------
1 | // # URL helper
2 | // Usage: `{{url}}`, `{{url absolute="true"}}`
3 | //
4 | // Returns the URL for the current object scope i.e. If inside a post scope will return post permalink
5 | // `absolute` flag outputs absolute URL, else URL is relative
6 |
7 | var config = require('../config'),
8 | schema = require('../data/schema').checks,
9 | url;
10 |
11 | url = function (options) {
12 | var absolute = options && options.hash.absolute;
13 |
14 | if (schema.isPost(this)) {
15 | return config.urlFor('post', {post: this}, absolute);
16 | }
17 |
18 | if (schema.isTag(this)) {
19 | return config.urlFor('tag', {tag: this}, absolute);
20 | }
21 |
22 | if (schema.isUser(this)) {
23 | return config.urlFor('author', {author: this}, absolute);
24 | }
25 |
26 | if (schema.isNav(this)) {
27 | return config.urlFor('nav', {nav: this}, absolute);
28 | }
29 |
30 | return config.urlFor(this, absolute);
31 | };
32 |
33 | module.exports = url;
34 |
--------------------------------------------------------------------------------
/core/client/app/routes/mobile-index-route.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | import mobileQuery from 'ghost/utils/mobile';
3 |
4 | // Routes that extend MobileIndexRoute need to implement
5 | // desktopTransition, a function which is called when
6 | // the user resizes to desktop levels.
7 | var MobileIndexRoute = Ember.Route.extend({
8 | desktopTransition: Ember.K,
9 |
10 | activate: function attachDesktopTransition() {
11 | this._super();
12 | mobileQuery.addListener(this.desktopTransitionMQ);
13 | },
14 |
15 | deactivate: function removeDesktopTransition() {
16 | this._super();
17 | mobileQuery.removeListener(this.desktopTransitionMQ);
18 | },
19 |
20 | setDesktopTransitionMQ: function () {
21 | var self = this;
22 | this.set('desktopTransitionMQ', function desktopTransitionMQ() {
23 | if (!mobileQuery.matches) {
24 | self.desktopTransition();
25 | }
26 | });
27 | }.on('init')
28 | });
29 |
30 | export default MobileIndexRoute;
31 |
--------------------------------------------------------------------------------
/core/client/app/routes/settings/code-injection.js:
--------------------------------------------------------------------------------
1 | import AuthenticatedRoute from 'ghost/routes/authenticated';
2 | import loadingIndicator from 'ghost/mixins/loading-indicator';
3 | import CurrentUserSettings from 'ghost/mixins/current-user-settings';
4 | import styleBody from 'ghost/mixins/style-body';
5 |
6 | var SettingsCodeInjectionRoute = AuthenticatedRoute.extend(styleBody, loadingIndicator, CurrentUserSettings, {
7 | classNames: ['settings-view-code'],
8 |
9 | beforeModel: function () {
10 | return this.get('session.user')
11 | .then(this.transitionAuthor())
12 | .then(this.transitionEditor());
13 | },
14 |
15 | model: function () {
16 | return this.store.find('setting', {type: 'blog,theme'}).then(function (records) {
17 | return records.get('firstObject');
18 | });
19 | },
20 |
21 | actions: {
22 | save: function () {
23 | this.get('controller').send('save');
24 | }
25 | }
26 | });
27 |
28 | export default SettingsCodeInjectionRoute;
29 |
--------------------------------------------------------------------------------
/core/client/app/templates/post-tags-input.hbs:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
13 |
14 |
15 | {{view view.tagInputView class="tag-input js-tag-input" id="tags" value=newTagText}}
16 |
23 |
24 |
--------------------------------------------------------------------------------
/core/client/app/routes/settings/general.js:
--------------------------------------------------------------------------------
1 | import AuthenticatedRoute from 'ghost/routes/authenticated';
2 | import loadingIndicator from 'ghost/mixins/loading-indicator';
3 | import CurrentUserSettings from 'ghost/mixins/current-user-settings';
4 | import styleBody from 'ghost/mixins/style-body';
5 |
6 | var SettingsGeneralRoute = AuthenticatedRoute.extend(styleBody, loadingIndicator, CurrentUserSettings, {
7 | titleToken: 'General',
8 |
9 | classNames: ['settings-view-general'],
10 |
11 | beforeModel: function () {
12 | return this.get('session.user')
13 | .then(this.transitionAuthor())
14 | .then(this.transitionEditor());
15 | },
16 |
17 | model: function () {
18 | return this.store.find('setting', {type: 'blog,theme'}).then(function (records) {
19 | return records.get('firstObject');
20 | });
21 | },
22 |
23 | actions: {
24 | save: function () {
25 | this.get('controller').send('save');
26 | }
27 | }
28 | });
29 |
30 | export default SettingsGeneralRoute;
31 |
--------------------------------------------------------------------------------
/core/shared/favicon.ico:
--------------------------------------------------------------------------------
1 | ( ( 0, @0, @0, @0, @ 0, @0, @0, @0, @ 0, @0, @0, @0, @ 0, @0, @0, @0, @ 0, @0, @0, @0, @0, @0, @0, @0, @0, @0, @ 0, @0, @0, @0, @0, @0, @0, @0, @0, @0, @ 0, @0, @0, @0, @0, @0, @ 0, @0, @ 0, @0, @0, @0, @0, @0, @ 0, @0, @
--------------------------------------------------------------------------------
/core/test/utils/fixtures/export/export-003-api-wrapper.json:
--------------------------------------------------------------------------------
1 | {"db":[{
2 | "meta": {
3 | "exported_on": 1388318311015,
4 | "version": "003"
5 | },
6 | "data": {
7 | "posts": [
8 | {
9 | "id": 1,
10 | "title": "Welcome to Ghost",
11 | "slug": "welcome-to-ghost",
12 | "markdown": "You're live! Nice.",
13 | "html": "You're live! Nice.
",
14 | "image": null,
15 | "featured": 0,
16 | "page": 0,
17 | "status": "published",
18 | "language": "en_US",
19 | "meta_title": null,
20 | "meta_description": null,
21 | "author_id": 2,
22 | "created_at": 1388318310782,
23 | "created_by": 1,
24 | "updated_at": 1388318310782,
25 | "updated_by": 1,
26 | "published_at": 1388318310783,
27 | "published_by": 2
28 | }
29 | ]
30 | }
31 | }]}
--------------------------------------------------------------------------------
/core/client/app/templates/signin.hbs:
--------------------------------------------------------------------------------
1 |
19 |
--------------------------------------------------------------------------------
/core/client/app/routes/settings/index.js:
--------------------------------------------------------------------------------
1 | import MobileIndexRoute from 'ghost/routes/mobile-index-route';
2 | import CurrentUserSettings from 'ghost/mixins/current-user-settings';
3 | import mobileQuery from 'ghost/utils/mobile';
4 |
5 | var SettingsIndexRoute = MobileIndexRoute.extend(SimpleAuth.AuthenticatedRouteMixin, CurrentUserSettings, {
6 | titleToken: 'Settings',
7 |
8 | // Redirect users without permission to view settings,
9 | // and show the settings.general route unless the user
10 | // is mobile
11 | beforeModel: function () {
12 | var self = this;
13 | return this.get('session.user')
14 | .then(this.transitionAuthor())
15 | .then(this.transitionEditor())
16 | .then(function () {
17 | if (!mobileQuery.matches) {
18 | self.transitionTo('settings.general');
19 | }
20 | });
21 | },
22 |
23 | desktopTransition: function () {
24 | this.transitionTo('settings.general');
25 | }
26 | });
27 |
28 | export default SettingsIndexRoute;
29 |
--------------------------------------------------------------------------------
/core/test/utils/fixtures/export/export-003-api-wrapper-bad.json:
--------------------------------------------------------------------------------
1 | {"asdadas":[{
2 | "meta": {
3 | "exported_on": 1388318311015,
4 | "version": "003"
5 | },
6 | "data": {
7 | "posts": [
8 | {
9 | "id": 1,
10 | "title": "Welcome to Ghost",
11 | "slug": "welcome-to-ghost",
12 | "markdown": "You're live! Nice.",
13 | "html": "You're live! Nice.
",
14 | "image": null,
15 | "featured": 0,
16 | "page": 0,
17 | "status": "published",
18 | "language": "en_US",
19 | "meta_title": null,
20 | "meta_description": null,
21 | "author_id": 2,
22 | "created_at": 1388318310782,
23 | "created_by": 1,
24 | "updated_at": 1388318310782,
25 | "updated_by": 1,
26 | "published_at": 1388318310783,
27 | "published_by": 2
28 | }
29 | ]
30 | }
31 | }]}
--------------------------------------------------------------------------------
/core/client/app/helpers/gh-format-html.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | /* global html_sanitize*/
3 | import cajaSanitizers from 'ghost/utils/caja-sanitizers';
4 |
5 | var formatHTML = Ember.HTMLBars.makeBoundHelper(function (arr /* hashParams */) {
6 | if (!arr || !arr.length) {
7 | return;
8 | }
9 |
10 | var escapedhtml = arr[0] || '';
11 |
12 | // replace script and iFrame
13 | escapedhtml = escapedhtml.replace(/
25 |
26 |
27 |
28 |
29 |
30 | {{content-for 'body-footer'}}
31 | {{content-for 'test-body-footer'}}
32 |