diff --git a/ghost/admin/app/components/gh-post-settings-menu.hbs b/ghost/admin/app/components/gh-post-settings-menu.hbs
index af6a24ea7b..897b79c798 100644
--- a/ghost/admin/app/components/gh-post-settings-menu.hbs
+++ b/ghost/admin/app/components/gh-post-settings-menu.hbs
@@ -79,10 +79,43 @@
{{#if this.showVisibilityInput}}
-
-
-
-
+ {{#if (feature "multipleProducts")}}
+
+
+
+
+
+
+ {{else}}
+
+
+
+
+ {{/if}}
{{/if}}
@@ -438,4 +471,4 @@
-
\ No newline at end of file
+
diff --git a/ghost/admin/app/components/gh-post-settings-menu.js b/ghost/admin/app/components/gh-post-settings-menu.js
index d6259f90f7..d1eb719a9c 100644
--- a/ghost/admin/app/components/gh-post-settings-menu.js
+++ b/ghost/admin/app/components/gh-post-settings-menu.js
@@ -153,6 +153,23 @@ export default Component.extend({
}
},
+ async setVisibility(segment) {
+ this.post.set('visibility', segment);
+ try {
+ await this.post.validate({property: 'visibility'});
+ if (this.post.changedAttributes().visibility) {
+ await this.savePostTask.perform();
+ }
+ } catch (e) {
+ if (!e) {
+ // validation error
+ return;
+ }
+
+ throw e;
+ }
+ },
+
setPublishedAtBlogTime(time) {
let post = this.post;
diff --git a/ghost/admin/app/models/post.js b/ghost/admin/app/models/post.js
index ecd3bac53c..342d0a5f3d 100644
--- a/ghost/admin/app/models/post.js
+++ b/ghost/admin/app/models/post.js
@@ -171,6 +171,24 @@ export default Model.extend(Comparable, ValidationEngine, {
return this.get('ghostPaths.url').join(blogUrl, previewKeyword, uuid);
}),
+ isPublic: computed('visibility', function () {
+ return this.visibility === 'public' ? true : false;
+ }),
+
+ visibilitySegment: computed('visibility', 'isPublic', function () {
+ if (this.isPublic) {
+ return this.settings.get('defaultContentVisibility') === 'paid' ? 'status:-free' : 'status:free,status:-free';
+ } else {
+ if (this.visibility === 'members') {
+ return 'status:free,status:-free';
+ }
+ if (this.visibility === 'paid') {
+ return 'status:-free';
+ }
+ return this.visibility;
+ }
+ }),
+
// check every second to see if we're past the scheduled time
// will only re-compute if this property is being observed elsewhere
pastScheduledTime: computed('isScheduled', 'publishedAtUTC', 'clock.second', function () {
diff --git a/ghost/admin/app/transforms/visibility-string.js b/ghost/admin/app/transforms/visibility-string.js
new file mode 100644
index 0000000000..f1f75bab65
--- /dev/null
+++ b/ghost/admin/app/transforms/visibility-string.js
@@ -0,0 +1,29 @@
+import Transform from '@ember-data/serializer/transform';
+
+// post visibility supports `'members'` and `'paid'` as special-case options
+// but that doesn't map well for options in our token select inputs so we
+// expand/convert them here to make usage elsewhere easier
+
+export default class VisibilityString extends Transform {
+ deserialize(serialized) {
+ if (serialized === 'members') {
+ return 'status:free,status:-free';
+ }
+ if (serialized === 'paid') {
+ return 'status:-free';
+ }
+
+ return serialized;
+ }
+
+ serialize(deserialized) {
+ if (deserialized === 'status:free,status:-free') {
+ return 'members';
+ }
+ if (deserialized === 'status:-free') {
+ return 'paid';
+ }
+
+ return deserialized;
+ }
+}
diff --git a/ghost/admin/app/validators/post.js b/ghost/admin/app/validators/post.js
index 5845198cd3..45f5718e40 100644
--- a/ghost/admin/app/validators/post.js
+++ b/ghost/admin/app/validators/post.js
@@ -67,6 +67,13 @@ export default BaseValidator.create({
}
},
+ visibility(model) {
+ if (isBlank(model.visibility) && !model.isNew) {
+ model.errors.add('visibility', 'A members group must be selected for members-only posts');
+ this.invalidate();
+ }
+ },
+
codeinjectionFoot(model) {
if (!validator.isLength(model.codeinjectionFoot || '', 0, 65535)) {
model.errors.add('codeinjectionFoot', 'Footer code cannot be longer than 65535 characters.');