diff --git a/ghost/admin/app/components/gh-tag-settings-form.js b/ghost/admin/app/components/gh-tag-settings-form.js index f2ecdfdd7e..9fbb5cda83 100644 --- a/ghost/admin/app/components/gh-tag-settings-form.js +++ b/ghost/admin/app/components/gh-tag-settings-form.js @@ -25,6 +25,7 @@ export default Component.extend({ isViewingSubview: false, + feature: service(), config: service(), mediaQueries: service(), @@ -114,11 +115,11 @@ export default Component.extend({ }, setCoverImage(image) { - invokeAction(this, 'setProperty', 'image', image); + this.send('setProperty', 'image', image); }, clearCoverImage() { - invokeAction(this, 'setProperty', 'image', ''); + this.send('setProperty', 'image', ''); }, openMeta() { diff --git a/ghost/admin/app/components/gh-tag.js b/ghost/admin/app/components/gh-tag.js index dfe4dd46c8..fd8a7ccb20 100644 --- a/ghost/admin/app/components/gh-tag.js +++ b/ghost/admin/app/components/gh-tag.js @@ -1,9 +1,14 @@ import Ember from 'ember'; import {invokeAction} from 'ember-invoke-action'; -const {Component} = Ember; +const { + inject: {service}, + Component +} = Ember; export default Component.extend({ + feature: service(), + willDestroyElement() { this._super(...arguments); diff --git a/ghost/admin/app/controllers/posts.js b/ghost/admin/app/controllers/posts.js index 6b34a9a87e..b5cfb97b3b 100644 --- a/ghost/admin/app/controllers/posts.js +++ b/ghost/admin/app/controllers/posts.js @@ -1,6 +1,11 @@ import Ember from 'ember'; -const {Controller, compare, computed} = Ember; +const { + Controller, + compare, + computed, + inject: {service} +} = Ember; const {equal} = computed; // a custom sort function is needed in order to sort the posts list the same way the server would: @@ -67,6 +72,7 @@ function publishedAtCompare(item1, item2) { } export default Controller.extend({ + feature: service(), showDeletePostModal: false, diff --git a/ghost/admin/app/mirage/factories/tag.js b/ghost/admin/app/mirage/factories/tag.js index 7a17d574bb..c924af13d3 100644 --- a/ghost/admin/app/mirage/factories/tag.js +++ b/ghost/admin/app/mirage/factories/tag.js @@ -5,7 +5,7 @@ export default Mirage.Factory.extend({ created_at() { return '2015-09-11T09:44:29.871Z'; }, created_by() { return 1; }, description(i) { return `Description for tag ${i}.`; }, - hidden() { return false; }, + visibility() { return 'public'; }, image(i) { return `/content/images/2015/10/tag-${i}.jpg`; }, meta_description(i) { return `Meta description for tag ${i}.`; }, meta_title(i) { return `Meta Title for tag ${i}`; }, diff --git a/ghost/admin/app/mirage/fixtures/settings.js b/ghost/admin/app/mirage/fixtures/settings.js index 40843c2347..7c1706fdde 100644 --- a/ghost/admin/app/mirage/fixtures/settings.js +++ b/ghost/admin/app/mirage/fixtures/settings.js @@ -125,7 +125,7 @@ export default [ id: 12, uuid: 'd806f358-7996-4c74-b153-8876959c4b70', key: 'labs', - value: '{"codeInjectionUI":true,"subscribers":true}', + value: '{"subscribers":true,"internalTags":true}', type: 'blog', created_at: '2015-01-12T18:29:01.000Z', created_by: 1, diff --git a/ghost/admin/app/models/post.js b/ghost/admin/app/models/post.js index 7e7adb67c3..fd8301d80d 100644 --- a/ghost/admin/app/models/post.js +++ b/ghost/admin/app/models/post.js @@ -9,7 +9,7 @@ const { computed, inject: {service} } = Ember; -const {equal} = computed; +const {equal, filterBy} = computed; export default Model.extend(ValidationEngine, { validationType: 'post', @@ -68,6 +68,7 @@ export default Model.extend(ValidationEngine, { isPublished: equal('status', 'published'), isDraft: equal('status', 'draft'), + internalTags: filterBy('tags', 'isInternal', true), // remove client-generated tags, which have `id: null`. // Ember Data won't recognize/update them automatically diff --git a/ghost/admin/app/models/tag.js b/ghost/admin/app/models/tag.js index abadebd4bf..d568a8b6d8 100644 --- a/ghost/admin/app/models/tag.js +++ b/ghost/admin/app/models/tag.js @@ -1,8 +1,17 @@ /* jscs:disable requireCamelCaseOrUpperCaseIdentifiers */ +import Ember from 'ember'; import Model from 'ember-data/model'; import attr from 'ember-data/attr'; import ValidationEngine from 'ghost-admin/mixins/validation-engine'; +const { + computed, + observer, + inject: {service} +} = Ember; + +const {equal} = computed; + export default Model.extend(ValidationEngine, { validationType: 'tag', @@ -14,10 +23,34 @@ export default Model.extend(ValidationEngine, { metaTitle: attr('string'), metaDescription: attr('string'), image: attr('string'), - hidden: attr('boolean'), + visibility: attr('string', {defaultValue: 'public'}), createdAt: attr('moment-utc'), updatedAt: attr('moment-utc'), createdBy: attr(), updatedBy: attr(), - count: attr('raw') + count: attr('raw'), + + isInternal: equal('visibility', 'internal'), + isPublic: equal('visibility', 'public'), + + feature: service(), + + setVisibility() { + let internalRegex = /^#.?/; + + this.set('visibility', internalRegex.test(this.get('name')) ? 'internal' : 'public'); + }, + + save() { + if (this.get('feature.internalTags') && this.get('changedAttributes.name') && !this.get('isDeleted')) { + this.setVisibility(); + } + return this._super(...arguments); + }, + + setVisibilityOnNew: observer('feature.internalTags', 'isNew', 'isSaving', 'name', function () { + if (this.get('isNew') && !this.get('isSaving') && this.get('feature.internalTags')) { + this.setVisibility(); + } + }) }); diff --git a/ghost/admin/app/services/feature.js b/ghost/admin/app/services/feature.js index 31c6099692..5d026225ec 100644 --- a/ghost/admin/app/services/feature.js +++ b/ghost/admin/app/services/feature.js @@ -31,6 +31,7 @@ export default Service.extend({ publicAPI: feature('publicAPI'), subscribers: feature('subscribers'), + internalTags: feature('internalTags'), _settings: null, diff --git a/ghost/admin/app/templates/components/gh-tag.hbs b/ghost/admin/app/templates/components/gh-tag.hbs index 92ba536b36..50ef895c41 100644 --- a/ghost/admin/app/templates/components/gh-tag.hbs +++ b/ghost/admin/app/templates/components/gh-tag.hbs @@ -2,6 +2,13 @@ {{#link-to 'settings.tags.tag' tag class="tag-edit-button"}} {{tag.name}} /{{tag.slug}} + + {{#if feature.internalTags}} + {{#if tag.isInternal}} + internal + {{/if}} + {{/if}} +

{{tag.description}}

{{tag.count.posts}} {{/link-to}} diff --git a/ghost/admin/app/templates/posts.hbs b/ghost/admin/app/templates/posts.hbs index c42ae4e199..fdd8f5b204 100644 --- a/ghost/admin/app/templates/posts.hbs +++ b/ghost/admin/app/templates/posts.hbs @@ -32,6 +32,11 @@ Draft {{/if}} + {{#if feature.internalTags}} + {{#each post.internalTags as |tag|}} + {{tag.name}} + {{/each}} + {{/if}} {{/link-to}} {{/gh-posts-list-item}} diff --git a/ghost/admin/app/templates/settings/labs.hbs b/ghost/admin/app/templates/settings/labs.hbs index 30ce9bb32a..e91237ec9a 100644 --- a/ghost/admin/app/templates/settings/labs.hbs +++ b/ghost/admin/app/templates/settings/labs.hbs @@ -53,6 +53,9 @@ {{#gh-feature-flag "subscribers"}} Subscribers - Collect email addresses from your readers, more info in the docs {{/gh-feature-flag}} + {{#gh-feature-flag "internalTags"}} + Internal Tags - tags which don't show up in your theme. more info in the docs. + {{/gh-feature-flag}} diff --git a/ghost/admin/tests/acceptance/settings/tags-test.js b/ghost/admin/tests/acceptance/settings/tags-test.js index 2656c72267..d992fc4b2b 100644 --- a/ghost/admin/tests/acceptance/settings/tags-test.js +++ b/ghost/admin/tests/acceptance/settings/tags-test.js @@ -285,6 +285,25 @@ describe('Acceptance: Settings - Tags', function () { }); }); + it('shows the internal tag label', function () { + server.create('tag', {name: '#internal-tag', slug: 'hash-internal-tag', visibility: 'internal'}); + + visit('settings/tags/'); + + andThen(() => { + expect(currentURL()).to.equal('/settings/tags/hash-internal-tag'); + + expect(find('.settings-tags .settings-tag').length, 'tag list count') + .to.equal(1); + + expect(find('.settings-tags .settings-tag:first .label.label-blue').length, 'internal tag label') + .to.equal(1); + + expect(find('.settings-tags .settings-tag:first .label.label-blue').text().trim(), 'internal tag label text') + .to.equal('internal'); + }); + }); + it('redirects to 404 when tag does not exist', function () { server.get('/tags/slug/unknown/', function () { return new Mirage.Response(404, {'Content-Type': 'application/json'}, {errors: [{message: 'Tag not found.', errorType: 'NotFoundError'}]});