🎨 Adjusted post settings menu design so it can stay open whilst editing

refs https://github.com/TryGhost/Team/issues/840

We wanted to switch to a settings menu that stays open to the right of the editor rather than a popover that blocks all other interaction with the post to solve two use-cases:

1.  when editing it's fairly common to select some text from the post contents when setting excerpt and meta data text, with the previous design not letting you scroll or select whilst the menu got in the way of that
2. having the menu open with meta data visible before publishing can help you see everything is set as you want and help you feel confident when publishing/sending content

---

- removed `psmRedesign` labs flag
- swapped labs component/css for main component and deleted labs component
- cleaned up now-unused `ui.showSettingsMenu` property and related actions/classes
This commit is contained in:
Kevin Ansfield 2021-07-02 19:03:52 +01:00
parent a07b40440d
commit 3d16b95e0f
18 changed files with 193 additions and 1424 deletions

View File

@ -1,3 +1,3 @@
<div class="gh-app" {{did-insert this.setBodyClass}} {{did-update this.setBodyClass @showSettingsMenu}} ...attributes>
<div class="gh-app" ...attributes>
{{yield}}
</div>

View File

@ -1,9 +0,0 @@
import Component from '@glimmer/component';
import {action} from '@ember/object';
export default class GhAppComponent extends Component {
@action
setBodyClass() {
document.body.classList.toggle('settings-menu-expanded', this.args.showSettingsMenu);
}
}

View File

@ -1,5 +1,4 @@
{{yield (hash
headerClass=this.headerClass
headerHeight=this.headerHeight
isDraggedOver=this.isDraggedOver
isFullScreen=this.isFullScreen

View File

@ -19,7 +19,6 @@ export default Component.extend({
// Internal attributes
droppedFiles: null,
headerClass: '',
headerHeight: 0,
imageExtensions: IMAGE_EXTENSIONS,
imageMimeTypes: IMAGE_MIME_TYPES,
@ -33,29 +32,10 @@ export default Component.extend({
_onResizeHandler: null,
_viewActionsWidth: 190,
init() {
this._super(...arguments);
this._onResizeHandler = (evt) => {
debounce(this, this._setHeaderClass, evt, 100);
};
},
didInsertElement() {
this._super(...arguments);
window.addEventListener('resize', this._onResizeHandler);
this._setHeaderClass();
},
willDestroyElement() {
this._super(...arguments);
window.removeEventListener('resize', this._onResizeHandler);
},
actions: {
toggleFullScreen(isFullScreen) {
this.set('isFullScreen', isFullScreen);
this.ui.set('isFullScreen', isFullScreen);
run.scheduleOnce('afterRender', this, this._setHeaderClass);
},
togglePreview(isPreview) {
@ -64,7 +44,6 @@ export default Component.extend({
toggleSplitScreen(isSplitScreen) {
this.set('isSplitScreen', isSplitScreen);
run.scheduleOnce('afterRender', this, this._setHeaderClass);
},
uploadImages(fileList, resetInput) {
@ -85,40 +64,6 @@ export default Component.extend({
}
},
_setHeaderClass() {
if (this.feature.psmRedesign) {
return;
}
let editorTitle = this.element.querySelector('.gh-editor-title, .kg-title-input');
let smallHeaderClass = 'gh-editor-header-small';
let newHeaderClass = '';
this._editorTitleElement = editorTitle;
if (this.isSplitScreen) {
this.set('headerClass', smallHeaderClass);
return;
}
if (editorTitle) {
let boundingRect = editorTitle.getBoundingClientRect();
let maxRight = window.innerWidth - this._viewActionsWidth;
if (boundingRect.right >= maxRight) {
newHeaderClass = smallHeaderClass;
}
}
if (newHeaderClass !== this.headerClass) {
// grab height of header so that we can pass it as an offset to other
// editor components
run.scheduleOnce('afterRender', this, this._setHeaderHeight);
}
this.set('headerClass', newHeaderClass);
},
_setHeaderHeight() {
if (this.headerClass && this._editorTitleElement) {
let height = this._editorTitleElement.offsetHeight;

View File

@ -1,467 +0,0 @@
<div
class="settings-menu-container-labs {{if (and this.isViewingSubview (not (eq this.subview "email-settings"))) "settings-menu-container-labs-wide"}}"
{{did-insert this.setSidebarWidthFromElement}}
{{did-update this.setSidebarWidthFromElement this.isViewingSubview}}
>
<div id="entry-controls">
<div class="settings-menu settings-menu-pane settings-menu-pane-main">
<div class="settings-menu-header labs">
<h4>{{capitalize this.post.displayName}} settings</h4>
</div>
<div class="settings-menu-content labs">
<form>
<div class="form-group">
<label for="url">{{capitalize this.post.displayName}} URL</label>
{{!-- new posts don't have a preview link --}}
{{#unless this.post.isNew}}
{{#if this.post.isPublished}}
<a class="post-view-link" target="_blank" href="{{this.post.url}}">
View {{this.post.displayName}} {{svg-jar "external"}}
</a>
{{else if this.post.isScheduled}}
<a class="post-view-link" target="_blank" href="{{this.post.previewUrl}}">
Preview {{svg-jar "external"}}
</a>
{{/if}}
{{/unless}}
<div class="gh-input-icon gh-icon-link">
{{svg-jar "link"}}
<GhTextInput
@class="post-setting-slug"
@id="url"
@name="post-setting-slug"
@value={{readonly this.slugValue}}
@input={{action (mut this.slugValue) value="target.value"}}
@focus-out={{action "updateSlug" this.slugValue}}
@stopEnterKeyDownPropagation={{true}} />
</div>
<GhUrlPreview @slug={{this.slugValue}} @tagName="p" @classNames="description" />
</div>
<div class="form-group">
{{#if (or this.post.isDraft this.post.isPublished this.post.pastScheduledTime)}}
<label>Publish date</label>
{{else}}
<label>Scheduled date</label>
{{/if}}
<GhDateTimePicker
@date={{this.post.publishedAtBlogDate}}
@time={{this.post.publishedAtBlogTime}}
@setDate={{action "setPublishedAtBlogDate"}}
@setTime={{action "setPublishedAtBlogTime"}}
@errors={{this.post.errors}}
@dateErrorProperty="publishedAtBlogDate"
@timeErrorProperty="publishedAtBlogTime"
@maxDate="now"
@disabled={{this.post.isScheduled}}
@isActive={{and this.showSettingsMenu (not this.isViewingSubview)}}
/>
{{#unless (or this.post.isDraft this.post.isPublished this.post.pastScheduledTime)}}
<p>Use the publish menu to re-schedule</p>
{{/unless}}
</div>
{{#unless this.session.user.isContributor}}
<div class="form-group">
<label for="tag-input">Tags</label>
<GhPsmTagsInput @post={{this.post}} @triggerId="tag-input" />
</div>
{{/unless}}
{{#if this.showVisibilityInput}}
{{#if (feature "multipleProducts")}}
<GhFormGroup @errors={{this.post.errors}} @hasValidated={{this.post.hasValidated}} @property="visibility">
<label for="visibility-input">Post access</label>
<div
class="gh-radio {{if this.post.isPublic "active"}}"
{{on "click" (action "setVisibility" "public")}}
>
<div class="gh-radio-button"></div>
<div class="gh-radio-content">
<div class="gh-radio-label">Public</div>
</div>
</div>
<div
class="gh-radio {{if (not this.post.isPublic) "active"}}"
{{on "click" (action "setVisibility" this.post.visibilitySegment)}}
>
<div class="gh-radio-button"></div>
<div class="gh-radio-content">
<div class="gh-radio-label">Members-only</div>
<div class="gh-radio-desc">
<GhMembersSegmentSelect
@hideLabels={{true}}
@segment={{this.post.visibilitySegment}}
@onChange={{action "setVisibility"}}
@renderInPlace={{true}}
@hideOptionsWhenAllSelected={{true}}
/>
</div>
</div>
</div>
<GhErrorMessage @errors={{this.post.errors}} @property="visibility" class="no-selection" data-test-error="visibility" />
</GhFormGroup>
{{else}}
<div class="form-group">
<label for="visibility-input">Post access</label>
<GhPsmVisibilityInput @post={{this.post}} @triggerId="visibility-input" />
</div>
{{/if}}
{{/if}}
<GhFormGroup @errors={{this.post.errors}} @hasValidated={{this.post.hasValidated}} @property="customExcerpt">
<label for="custom-excerpt">Excerpt</label>
<GhTextarea
@class="post-setting-custom-excerpt"
@id="custom-excerpt"
@name="post-setting-custom-excerpt"
@value={{readonly this.customExcerptScratch}}
@input={{action (mut this.customExcerptScratch) value="target.value"}}
@focus-out={{action "setCustomExcerpt" this.customExcerptScratch}}
@stopEnterKeyDownPropagation="true"
data-test-field="custom-excerpt"
/>
<GhErrorMessage @errors={{this.post.errors}} @property="customExcerpt" data-test-error="custom-excerpt" />
</GhFormGroup>
{{#unless this.session.user.isAuthorOrContributor}}
<GhFormGroup @class="for-select" @errors={{this.post.errors}} @hasValidated={{this.post.hasValidated}} @property="authors" data-test-input="authors">
<label for="author-list">Authors</label>
<GhPsmAuthorsInput @selectedAuthors={{this.post.authors}} @updateAuthors={{action "changeAuthors"}} @triggerId="author-list" />
<GhErrorMessage @errors={{this.post.errors}} @property="authors" data-test-error="authors" />
</GhFormGroup>
{{/unless}}
<ul class="nav-list nav-list-block">
<li class="nav-list-item" {{action "showSubview" "meta-data"}} data-test-button="meta-data">
<button type="button">
<b>Meta data</b>
<span>Extra content for search engines</span>
</button>
{{svg-jar "arrow-right"}}
</li>
<li class="nav-list-item" {{action "showSubview" "twitter-data"}} data-test-button="twitter-data">
<button type="button">
<b>Twitter card</b>
<span>Customize structured data for Twitter</span>
</button>
{{svg-jar "arrow-right"}}
</li>
<li class="nav-list-item" {{action "showSubview" "facebook-data"}} data-test-button="facebook-data">
<button type="button">
<b>Facebook card</b>
<span>Customize Open Graph data</span>
</button>
{{svg-jar "arrow-right"}}
</li>
{{#if (and this.post.isPost showEmailNewsletter)}}
<li class="nav-list-item" {{action "showSubview" "email-settings"}} data-test-button="email-settings">
<button type="button">
<b>Email newsletter</b>
<span>Customize email settings</span>
</button>
{{svg-jar "arrow-right"}}
</li>
{{/if}}
<li class="nav-list-item" {{action "showSubview" "codeinjection"}} data-test-button="codeinjection">
<button type="button">
<b>Code injection</b>
<span>Add styles/scripts to the header &amp; footer</span>
</button>
{{svg-jar "arrow-right"}}
</li>
</ul>
{{#unless this.session.user.isAuthorOrContributor}}
<div class="form-group for-checkbox">
<label class="checkbox" for="featured" {{action "toggleFeatured" bubbles="false"}}>
<input
type="checkbox"
checked={{this.post.featured}}
class="gh-input post-settings-featured"
onclick={{action (mut this.post.featured) value="target.checked"}}
data-test-checkbox="featured"
>
<span class="input-toggle-component"></span>
<p>Feature this {{this.post.displayName}}</p>
</label>
</div>
{{/unless}}
<GhPsmTemplateSelect
@post={{this.post}}
@onTemplateSelect={{action (mut this.post.customTemplate)}} />
{{#unless this.post.isNew}}
<button type="button" class="gh-btn gh-btn-hover-red gh-btn-icon settings-menu-delete-button" {{action "deletePost"}}><span>{{svg-jar "trash"}} Delete {{this.post.displayName}}</span></button>
{{/unless}}
</form>
</div>{{! .settings-menu-content }}
</div>{{! .post-settings-menu }}
{{#if this.isViewingSubview}}
<div class="settings-menu settings-menu-pane {{unless (eq this.subview "email-settings") "settings-menu-pane-wide"}}">
<div class="active">
{{#if (eq this.subview "meta-data")}}
<div class="settings-menu-header subview labs">
<button aria-label="Back" {{action "closeSubview"}} class="back settings-menu-header-action" data-test-button="close-psm-subview">{{svg-jar "arrow-left"}}<span class="hidden">Back</span></button>
<h4>Meta data</h4>
<div style="width:23px;"></div>
</div>
<div class="settings-menu-content labs">
<form {{action "discardEnter" on="submit"}}>
<GhFormGroup @errors={{this.post.errors}} @hasValidated={{this.post.hasValidated}} @property="metaTitle">
<label for="meta-title">Meta title</label>
<GhTextInput
@class="post-setting-meta-title"
@id="meta-title"
@name="post-setting-meta-title"
@placeholder={{this.seoTitle}}
@value={{readonly this.metaTitleScratch}}
@input={{action (mut this.metaTitleScratch) value="target.value"}}
@focus-out={{action "setMetaTitle" this.metaTitleScratch}}
@stopEnterKeyDownPropagation={{true}}
data-test-field="meta-title" />
<p>Recommended: <b>60</b> characters. Youve used {{gh-count-down-characters this.metaTitleScratch 60}}</p>
<GhErrorMessage @errors={{this.post.errors}} @property="meta-title" />
</GhFormGroup>
<GhFormGroup @errors={{this.post.errors}} @hasValidated={{this.post.hasValidated}} @property="metaDescription">
<label for="meta-description">Meta description</label>
<GhTextarea
@class="post-setting-meta-description"
@id="meta-description"
@name="post-setting-meta-description"
@placeholder={{truncate this.seoDescription 150}}
@value={{readonly this.metaDescriptionScratch}}
@input={{action (mut this.metaDescriptionScratch) value="target.value"}}
@focus-out={{action "setMetaDescription" this.metaDescriptionScratch}}
@stopEnterKeyDownPropagation="true"
data-test-field="meta-description" />
<p>Recommended: <b>145</b> characters. Youve used {{gh-count-down-characters this.metaDescriptionScratch 145}}</p>
<GhErrorMessage @errors={{this.post.errors}} @property="meta-description" />
</GhFormGroup>
<GhFormGroup @errors={{this.post.errors}} @hasValidated={{this.post.hasValidated}} @property="canonicalUrl">
<label for="canonicalUrl">Canonical URL</label>
<GhTextInput
@class="post-setting-canonicalUrl"
@name="post-setting-canonicalUrl"
@value={{readonly this.canonicalUrlScratch}}
@input={{action (mut this.canonicalUrlScratch) value="target.value"}}
@focus-out={{action "setCanonicalUrl" this.canonicalUrlScratch}}
@stopEnterKeyDownPropagation="true"
data-test-field="canonicalUrl" />
<GhErrorMessage @errors={{this.post.errors}} @property="canonicalUrl" />
</GhFormGroup>
<div class="form-group">
<label>Search Engine Result Preview</label>
<div class="gh-seo-container">
<div class="gh-seo-preview">
<div class="flex mb7">
{{svg-jar "google"}}
<div class="gh-seo-search-bar">{{svg-jar "google-search"}}</div>
</div>
<div class="gh-seo-preview-link">{{this.seoURL}}</div>
<div class="gh-seo-preview-title">{{truncate this.seoTitle 60}}</div>
<div class="gh-seo-preview-desc">{{moment-format (now) "DD MMM YYYY"}}{{truncate this.seoDescription 149}}</div>
</div>
</div>
</div>
</form>
</div>
{{/if}}
{{#if (eq this.subview "twitter-data")}}
<div class="settings-menu-header subview labs">
<button aria-label="Back" {{action "closeSubview"}} class="back settings-menu-header-action" data-test-button="close-psm-subview">{{svg-jar "arrow-left"}}<span class="hidden">Back</span></button>
<h4>Twitter card</h4>
<div style="width:23px;"></div>
</div>
<div class="settings-menu-content labs">
<form {{action "discardEnter" on="submit"}}>
<GhImageUploaderWithPreview
@image={{this.post.twitterImage}}
@text="Add Twitter image"
@allowUnsplash={{true}}
@update={{action "setTwitterImage"}}
@remove={{action "clearTwitterImage"}}
/>
<GhFormGroup @errors={{this.post.errors}} @hasValidated={{this.post.hasValidated}} @property="twitterTitle">
<label for="twitter-title">Twitter title</label>
<GhTextInput
@class="post-setting-twitter-title"
@id="twitter-title"
@name="post-setting-twitter-title"
@placeholder={{truncate this.twitterTitle 40}}
@value={{readonly this.twitterTitleScratch}}
@input={{action (mut this.twitterTitleScratch) value="target.value"}}
@focus-out={{action "setTwitterTitle" this.twitterTitleScratch}}
@stopEnterKeyDownPropagation={{true}}
data-test-field="twitter-title" />
<GhErrorMessage @errors={{this.post.errors}} @property="twitterTitle" data-test-error="twitter-title" />
</GhFormGroup>
<GhFormGroup @errors={{this.post.errors}} @hasValidated={{this.post.hasValidated}} @property="twitterDescription">
<label for="twitter-description">Twitter description</label>
<GhTextarea
@class="post-setting-twitter-description"
@id="twitter-description"
@name="post-setting-twitter-description"
@placeholder={{truncate this.twitterDescription 150}}
@stopEnterKeyDownPropagation="true"
@value={{readonly this.twitterDescriptionScratch}}
@input={{action (mut this.twitterDescriptionScratch) value="target.value"}}
@focus-out={{action "setTwitterDescription" this.twitterDescriptionScratch}}
data-test-field="twitter-description" />
<GhErrorMessage @errors={{this.post.errors}} @property="twitterDescription" data-test-error="twitter-description" />
</GhFormGroup>
<div class="form-group">
<label>Twitter preview</label>
<div class="gh-social-twitter-post-preview">
{{#if this.twitterImage}}
<div class="gh-social-twitter-preview-image" style={{background-image-style this.twitterImage}}></div>
{{/if}}
<div class="gh-social-twitter-preview-content">
<div class="gh-social-twitter-preview-title">{{this.twitterTitle}}</div>
<div class="gh-social-twitter-preview-desc">{{truncate this.twitterDescription}}</div>
<div class="gh-social-twitter-preview-meta">
{{svg-jar "twitter-link"}}
{{this.config.blogDomain}}
</div>
</div>
</div>
</div>
</form>
</div>
{{/if}}
{{#if (eq this.subview "email-settings")}}
<GhPostSettingsMenu::Email
@post={{this.post}}
@savePostTask={{this.savePostTask}}
@toggleEmailPreviewModal={{this.toggleEmailPreviewModal}}
@close={{action "closeSubview"}}
/>
{{/if}}
{{#if (eq this.subview "facebook-data")}}
<div class="settings-menu-header subview labs">
<button aria-label="Back" {{action "closeSubview"}} class="back settings-menu-header-action" data-test-button="close-psm-subview">{{svg-jar "arrow-left"}}<span class="hidden">Back</span></button>
<h4>Facebook card</h4>
<div style="width:23px;"></div>
</div>
<div class="settings-menu-content labs">
<form {{action "discardEnter" on="submit"}}>
<GhImageUploaderWithPreview
@image={{this.post.ogImage}}
@text="Add Facebook image"
@allowUnsplash={{true}}
@update={{action "setOgImage"}}
@remove={{action "clearOgImage"}}
/>
<GhFormGroup @errors={{this.post.errors}} @hasValidated={{this.post.hasValidated}} @property="ogTitle">
<label for="og-title">Facebook title</label>
<GhTextInput
@class="post-setting-og-title"
@id="og-title"
@name="post-setting-og-title"
@placeholder={{truncate this.facebookTitle 40}}
@value={{readonly this.ogTitleScratch}}
@input={{action (mut this.ogTitleScratch) value="target.value"}}
@focus-out={{action "setOgTitle" this.ogTitleScratch}}
@stopEnterKeyDownPropagation={{true}}
data-test-field="og-title" />
<GhErrorMessage @errors={{this.post.errors}} @property="ogTitle" data-test-error="og-title" />
</GhFormGroup>
<GhFormGroup @errors={{this.post.errors}} @hasValidated={{this.post.hasValidated}} @property="ogDescription">
<label for="og-description">Facebook description</label>
<GhTextarea
@class="post-setting-og-description"
@id="og-description" @name="post-setting-og-description"
@placeholder={{truncate this.facebookDescription 150}}
@value={{readonly this.ogDescriptionScratch}}
@input={{action (mut this.ogDescriptionScratch) value="target.value"}}
@focus-out={{action "setOgDescription" this.ogDescriptionScratch}}
@stopEnterKeyDownPropagation="true"
data-test-field="og-description" />
<GhErrorMessage @errors={{this.post.errors}} @property="ogDescription" data-test-error="og-description" />
</GhFormGroup>
<div class="form-group">
<label>Facebook preview</label>
<div class="gh-social-og-preview no-container">
{{#if this.facebookImage}}
<div class="gh-social-og-preview-image" style={{background-image-style this.facebookImage}}></div>
{{/if}}
<div class="gh-social-og-preview-bookmark">
{{!-- Ensures description is hidden if title exceeds one line --}}
<div class="gh-social-og-preview-content">
<div class="gh-social-og-preview-meta">
{{this.config.blogDomain}}
</div>
<div class="gh-social-og-preview-title">{{truncate this.facebookTitle}}</div>
<div class="gh-social-og-preview-desc">{{truncate this.facebookDescription}}</div>
</div>
</div>
</div>
</div>
</form>
</div>
{{/if}}
{{#if (eq this.subview "codeinjection")}}
<div class="settings-menu-header subview labs">
<button aria-label="Back" {{action "closeSubview"}} class="back settings-menu-header-action" data-test-button="close-psm-subview">{{svg-jar "arrow-left"}}<span class="hidden">Back</span></button>
<h4>Code injection</h4>
<div style="width:23px;"></div>
</div>
<div class="settings-menu-content labs settings-menu-content-codeinjection">
<form {{action "discardEnter" on="submit"}}>
<GhFormGroup @errors={{this.post.errors}} @hasValidated={{this.post.hasValidated}} @property="codeinjectionHead">
<label for="codeinjection-head">{{capitalize this.post.displayName}} header <code>\{{ghost_head}}</code></label>
<GhCmEditor @value={{this.codeinjectionHeadScratch}}
@id="post-setting-codeinjection-head"
@class="post-setting-codeinjection"
@name="post-setting-codeinjection-head"
@focusOut={{action "setHeaderInjection" this.codeinjectionHeadScratch}}
@stopEnterKeyDownPropagation="true"
@update={{action (mut this.codeinjectionHeadScratch)}}
data-test-field="codeinjection-head" />
<GhErrorMessage @errors={{this.post.errors}} @property="codeinjectionHead" data-test-error="codeinjection-head" />
</GhFormGroup>
<GhFormGroup @errors={{this.post.errors}} @hasValidated={{this.post.hasValidated}} @property="codeinjectionFoot">
<label for="codeinjection-foot">{{capitalize this.post.displayName}} footer <code>\{{ghost_foot}}</code></label>
<GhCmEditor @value={{this.codeinjectionFootScratch}}
@id="post-setting-codeinjection-foot"
@class="post-setting-codeinjection"
@name="post-setting-codeinjection-foot"
@focusOut={{action "setFooterInjection" this.codeinjectionFootScratch}}
@stopEnterKeyDownPropagation="true"
@update={{action (mut this.codeinjectionFootScratch)}}
data-test-field="codeinjection-foot" />
<GhErrorMessage @errors={{this.post.errors}} @property="codeinjectionFoot" data-test-error="codeinjection-foot" />
</GhFormGroup>
</form>
</div>
{{/if}}
</div>
</div>
{{/if}}
</div>
</div>

View File

@ -1,515 +0,0 @@
import Component from '@ember/component';
import boundOneWay from 'ghost-admin/utils/bound-one-way';
import moment from 'moment';
import {action} from '@ember/object';
import {alias, or} from '@ember/object/computed';
import {computed} from '@ember/object';
import {inject as service} from '@ember/service';
export default Component.extend({
feature: service(),
store: service(),
config: service(),
ajax: service(),
ghostPaths: service(),
notifications: service(),
slugGenerator: service(),
session: service(),
settings: service(),
ui: service(),
tagName: '',
post: null,
showSettingsMenu: false,
_showSettingsMenu: false,
canonicalUrlScratch: alias('post.canonicalUrlScratch'),
customExcerptScratch: alias('post.customExcerptScratch'),
codeinjectionFootScratch: alias('post.codeinjectionFootScratch'),
codeinjectionHeadScratch: alias('post.codeinjectionHeadScratch'),
metaDescriptionScratch: alias('post.metaDescriptionScratch'),
metaTitleScratch: alias('post.metaTitleScratch'),
ogDescriptionScratch: alias('post.ogDescriptionScratch'),
ogTitleScratch: alias('post.ogTitleScratch'),
twitterDescriptionScratch: alias('post.twitterDescriptionScratch'),
twitterTitleScratch: alias('post.twitterTitleScratch'),
slugValue: boundOneWay('post.slug'),
seoDescription: or('metaDescriptionScratch', 'customExcerptScratch', 'post.excerpt'),
facebookDescription: or('ogDescriptionScratch', 'customExcerptScratch', 'seoDescription', 'post.excerpt', 'settings.description', ''),
facebookImage: or('post.ogImage', 'post.featureImage', 'settings.ogImage', 'settings.coverImage'),
facebookTitle: or('ogTitleScratch', 'seoTitle'),
twitterDescription: or('twitterDescriptionScratch', 'customExcerptScratch', 'seoDescription', 'post.excerpt', 'settings.description', ''),
twitterImage: or('post.twitterImage', 'post.featureImage', 'settings.twitterImage', 'settings.coverImage'),
twitterTitle: or('twitterTitleScratch', 'seoTitle'),
showVisibilityInput: or('session.user.isOwner', 'session.user.isAdmin', 'session.user.isEditor'),
showEmailNewsletter: or('session.user.isOwner', 'session.user.isAdmin', 'session.user.isEditor'),
seoTitle: computed('metaTitleScratch', 'post.titleScratch', function () {
return this.metaTitleScratch || this.post.titleScratch || '(Untitled)';
}),
seoURL: computed('post.{slug,canonicalUrl}', 'config.blogUrl', function () {
const urlParts = [];
if (this.post.canonicalUrl) {
const canonicalUrl = new URL(this.post.canonicalUrl);
urlParts.push(canonicalUrl.host);
urlParts.push(...canonicalUrl.pathname.split('/').reject(p => !p));
} else {
const blogUrl = new URL(this.config.get('blogUrl'));
urlParts.push(blogUrl.host);
urlParts.push(...blogUrl.pathname.split('/').reject(p => !p));
urlParts.push(this.post.slug);
}
return urlParts.join(' > ');
}),
isViewingSubview: computed('showSettingsMenu', {
get() {
return false;
},
set(key, value) {
// Not viewing a subview if we can't even see the PSM
if (!this.showSettingsMenu) {
return false;
}
return value;
}
}),
didReceiveAttrs() {
this._super(...arguments);
// fired when menu is closed
if (!this.showSettingsMenu && this._showSettingsMenu) {
let post = this.post;
let errors = post.get('errors');
// reset the publish date if it has an error
if (errors.has('publishedAtBlogDate') || errors.has('publishedAtBlogTime')) {
post.set('publishedAtBlogTZ', post.get('publishedAtUTC'));
post.validate({attribute: 'publishedAtBlog'});
}
}
this._showSettingsMenu = this.showSettingsMenu;
},
willDestroyElement() {
this.setSidebarWidthVariable(0);
},
actions: {
showSubview(subview) {
this.set('isViewingSubview', true);
this.set('subview', subview);
},
closeSubview() {
this.set('isViewingSubview', false);
this.set('subview', null);
},
discardEnter() {
return false;
},
toggleFeatured() {
this.toggleProperty('post.featured');
// If this is a new post. Don't save the post. Defer the save
// to the user pressing the save button
if (this.get('post.isNew')) {
return;
}
this.savePostTask.perform().catch((error) => {
this.showError(error);
this.post.rollbackAttributes();
});
},
/**
* triggered by user manually changing slug
*/
updateSlug(newSlug) {
return this.updateSlugTask
.perform(newSlug)
.catch((error) => {
this.showError(error);
this.post.rollbackAttributes();
});
},
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;
}
},
setPublishedAtBlogDate(date) {
let post = this.post;
let dateString = moment(date).format('YYYY-MM-DD');
post.get('errors').remove('publishedAtBlogDate');
if (post.get('isNew') || date === post.get('publishedAtBlogDate')) {
post.validate({property: 'publishedAtBlog'});
} else {
post.set('publishedAtBlogDate', dateString);
return this.savePostTask.perform();
}
},
setPublishedAtBlogTime(time) {
let post = this.post;
post.get('errors').remove('publishedAtBlogDate');
if (post.get('isNew') || time === post.get('publishedAtBlogTime')) {
post.validate({property: 'publishedAtBlog'});
} else {
post.set('publishedAtBlogTime', time);
return this.savePostTask.perform();
}
},
setCustomExcerpt(excerpt) {
let post = this.post;
let currentExcerpt = post.get('customExcerpt');
if (excerpt === currentExcerpt) {
return;
}
post.set('customExcerpt', excerpt);
return post.validate({property: 'customExcerpt'}).then(() => this.savePostTask.perform());
},
setHeaderInjection(code) {
let post = this.post;
let currentCode = post.get('codeinjectionHead');
if (code === currentCode) {
return;
}
post.set('codeinjectionHead', code);
return post.validate({property: 'codeinjectionHead'}).then(() => this.savePostTask.perform());
},
setFooterInjection(code) {
let post = this.post;
let currentCode = post.get('codeinjectionFoot');
if (code === currentCode) {
return;
}
post.set('codeinjectionFoot', code);
return post.validate({property: 'codeinjectionFoot'}).then(() => this.savePostTask.perform());
},
setMetaTitle(metaTitle) {
// Grab the post and current stored meta title
let post = this.post;
let currentTitle = post.get('metaTitle');
// If the title entered matches the stored meta title, do nothing
if (currentTitle === metaTitle) {
return;
}
// If the title entered is different, set it as the new meta title
post.set('metaTitle', metaTitle);
// Make sure the meta title is valid and if so, save it into the post
return post.validate({property: 'metaTitle'}).then(() => {
if (post.get('isNew')) {
return;
}
return this.savePostTask.perform();
});
},
setMetaDescription(metaDescription) {
// Grab the post and current stored meta description
let post = this.post;
let currentDescription = post.get('metaDescription');
// If the title entered matches the stored meta title, do nothing
if (currentDescription === metaDescription) {
return;
}
// If the title entered is different, set it as the new meta title
post.set('metaDescription', metaDescription);
// Make sure the meta title is valid and if so, save it into the post
return post.validate({property: 'metaDescription'}).then(() => {
if (post.get('isNew')) {
return;
}
return this.savePostTask.perform();
});
},
setCanonicalUrl(value) {
// Grab the post and current stored meta description
let post = this.post;
let currentCanonicalUrl = post.canonicalUrl;
// If the value entered matches the stored value, do nothing
if (currentCanonicalUrl === value) {
return;
}
// If the value supplied is different, set it as the new value
post.set('canonicalUrl', value);
// Make sure the value is valid and if so, save it into the post
return post.validate({property: 'canonicalUrl'}).then(() => {
if (post.get('isNew')) {
return;
}
return this.savePostTask.perform();
});
},
setOgTitle(ogTitle) {
// Grab the post and current stored facebook title
let post = this.post;
let currentTitle = post.get('ogTitle');
// If the title entered matches the stored facebook title, do nothing
if (currentTitle === ogTitle) {
return;
}
// If the title entered is different, set it as the new facebook title
post.set('ogTitle', ogTitle);
// Make sure the facebook title is valid and if so, save it into the post
return post.validate({property: 'ogTitle'}).then(() => {
if (post.get('isNew')) {
return;
}
return this.savePostTask.perform();
});
},
setOgDescription(ogDescription) {
// Grab the post and current stored facebook description
let post = this.post;
let currentDescription = post.get('ogDescription');
// If the title entered matches the stored facebook description, do nothing
if (currentDescription === ogDescription) {
return;
}
// If the description entered is different, set it as the new facebook description
post.set('ogDescription', ogDescription);
// Make sure the facebook description is valid and if so, save it into the post
return post.validate({property: 'ogDescription'}).then(() => {
if (post.get('isNew')) {
return;
}
return this.savePostTask.perform();
});
},
setTwitterTitle(twitterTitle) {
// Grab the post and current stored twitter title
let post = this.post;
let currentTitle = post.get('twitterTitle');
// If the title entered matches the stored twitter title, do nothing
if (currentTitle === twitterTitle) {
return;
}
// If the title entered is different, set it as the new twitter title
post.set('twitterTitle', twitterTitle);
// Make sure the twitter title is valid and if so, save it into the post
return post.validate({property: 'twitterTitle'}).then(() => {
if (post.get('isNew')) {
return;
}
return this.savePostTask.perform();
});
},
setTwitterDescription(twitterDescription) {
// Grab the post and current stored twitter description
let post = this.post;
let currentDescription = post.get('twitterDescription');
// If the description entered matches the stored twitter description, do nothing
if (currentDescription === twitterDescription) {
return;
}
// If the description entered is different, set it as the new twitter description
post.set('twitterDescription', twitterDescription);
// Make sure the twitter description is valid and if so, save it into the post
return post.validate({property: 'twitterDescription'}).then(() => {
if (post.get('isNew')) {
return;
}
return this.savePostTask.perform();
});
},
setCoverImage(image) {
this.set('post.featureImage', image);
if (this.get('post.isNew')) {
return;
}
this.savePostTask.perform().catch((error) => {
this.showError(error);
this.post.rollbackAttributes();
});
},
clearCoverImage() {
this.set('post.featureImage', '');
if (this.get('post.isNew')) {
return;
}
this.savePostTask.perform().catch((error) => {
this.showError(error);
this.post.rollbackAttributes();
});
},
setOgImage(image) {
this.set('post.ogImage', image);
if (this.get('post.isNew')) {
return;
}
this.savePostTask.perform().catch((error) => {
this.showError(error);
this.post.rollbackAttributes();
});
},
clearOgImage() {
this.set('post.ogImage', '');
if (this.get('post.isNew')) {
return;
}
this.savePostTask.perform().catch((error) => {
this.showError(error);
this.post.rollbackAttributes();
});
},
setTwitterImage(image) {
this.set('post.twitterImage', image);
if (this.get('post.isNew')) {
return;
}
this.savePostTask.perform().catch((error) => {
this.showError(error);
this.post.rollbackAttributes();
});
},
clearTwitterImage() {
this.set('post.twitterImage', '');
if (this.get('post.isNew')) {
return;
}
this.savePostTask.perform().catch((error) => {
this.showError(error);
this.post.rollbackAttributes();
});
},
changeAuthors(newAuthors) {
let post = this.post;
// return if nothing changed
if (newAuthors.mapBy('id').join() === post.get('authors').mapBy('id').join()) {
return;
}
post.set('authors', newAuthors);
post.validate({property: 'authors'});
// if this is a new post (never been saved before), don't try to save it
if (post.get('isNew')) {
return;
}
this.savePostTask.perform().catch((error) => {
this.showError(error);
post.rollbackAttributes();
});
},
deletePost() {
if (this.deletePost) {
this.deletePost();
}
}
},
showError(error) {
// TODO: remove null check once ValidationEngine has been removed
if (error) {
this.notifications.showAPIError(error);
}
},
setSidebarWidthFromElement: action(function (element) {
const width = element.getBoundingClientRect().width;
this.setSidebarWidthVariable(width);
}),
setSidebarWidthVariable(width) {
document.documentElement.style.setProperty('--editor-sidebar-width', `${width}px`);
}
});

View File

@ -1,11 +1,12 @@
<div class="settings-menu-container {{if (and this.isViewingSubview (not (eq this.subview "email-settings"))) "settings-menu-container-wide"}}">
<div
class="settings-menu-container {{if (and this.isViewingSubview (not (eq this.subview "email-settings"))) "settings-menu-container-wide"}}"
{{did-insert this.setSidebarWidthFromElement}}
{{did-update this.setSidebarWidthFromElement this.isViewingSubview}}
>
<div id="entry-controls">
<div class="{{if this.isViewingSubview 'settings-menu-pane-out-left' 'settings-menu-pane-in'}} settings-menu settings-menu-pane">
<div class="settings-menu settings-menu-pane settings-menu-pane-main">
<div class="settings-menu-header">
<h4>{{capitalize this.post.displayName}} settings</h4>
<button aria-label="Close" class="close settings-menu-header-action" {{action "closeMenus" target=this.ui}} data-test-close-settings-menu>
{{svg-jar "close"}}<span class="hidden">Close</span>
</button>
</div>
<div class="settings-menu-content">
<form>
@ -17,7 +18,7 @@
<a class="post-view-link" target="_blank" href="{{this.post.url}}">
View {{this.post.displayName}} {{svg-jar "external"}}
</a>
{{else}}
{{else if this.post.isScheduled}}
<a class="post-view-link" target="_blank" href="{{this.post.previewUrl}}">
Preview {{svg-jar "external"}}
</a>
@ -202,9 +203,9 @@
</div>{{! .settings-menu-content }}
</div>{{! .post-settings-menu }}
<div class="{{if this.isViewingSubview 'settings-menu-pane-in' 'settings-menu-pane-out-right'}} settings-menu settings-menu-pane {{unless (eq this.subview "email-settings") "settings-menu-pane-wide"}}">
<div class="active">
{{#if this.isViewingSubview}}
<div class="settings-menu settings-menu-pane {{unless (eq this.subview "email-settings") "settings-menu-pane-wide"}}">
<div class="active">
{{#if (eq this.subview "meta-data")}}
<div class="settings-menu-header subview">
<button aria-label="Back" {{action "closeSubview"}} class="back settings-menu-header-action" data-test-button="close-psm-subview">{{svg-jar "arrow-left"}}<span class="hidden">Back</span></button>
@ -459,8 +460,8 @@
</form>
</div>
{{/if}}
</div>
</div>
{{/if}}
</div>
</div>
</div>
</div>

View File

@ -1,6 +1,7 @@
import Component from '@ember/component';
import boundOneWay from 'ghost-admin/utils/bound-one-way';
import moment from 'moment';
import {action} from '@ember/object';
import {alias, or} from '@ember/object/computed';
import {computed} from '@ember/object';
import {inject as service} from '@ember/service';
@ -17,6 +18,8 @@ export default Component.extend({
settings: service(),
ui: service(),
tagName: '',
post: null,
showSettingsMenu: false,
@ -97,6 +100,10 @@ export default Component.extend({
this._showSettingsMenu = this.showSettingsMenu;
},
willDestroyElement() {
this.setSidebarWidthVariable(0);
},
actions: {
showSubview(subview) {
this.set('isViewingSubview', true);
@ -495,5 +502,14 @@ export default Component.extend({
if (error) {
this.notifications.showAPIError(error);
}
},
setSidebarWidthFromElement: action(function (element) {
const width = element.getBoundingClientRect().width;
this.setSidebarWidthVariable(width);
}),
setSidebarWidthVariable(width) {
document.documentElement.style.setProperty('--editor-sidebar-width', `${width}px`);
}
});

View File

@ -1,10 +1,10 @@
<div class="settings-menu-header subview {{if (feature "psmRedesign") "labs"}}">
<div class="settings-menu-header subview">
<button {{on "click" this.close}} class="back settings-menu-header-action" data-test-button="close-psm-subview">{{svg-jar "arrow-left"}}<span class="hidden">Back</span></button>
<h4>Email newsletter</h4>
<div style="width:23px;"></div>
</div>
<div class="settings-menu-content settings-menu-email {{if (feature "psmRedesign") "labs"}}">
<div class="settings-menu-content settings-menu-email">
{{#if this.post.email.isSuccess}}
{{!-- Mail has already been sent --}}
<div class="ba b--whitegrey bg-white br3">

View File

@ -55,7 +55,6 @@ export default Service.extend({
launchComplete: feature('launchComplete', {user: true}),
matchHelper: feature('matchHelper'),
multipleProducts: feature('multipleProducts', {developer: true}),
psmRedesign: feature('psmRedesign', {developer: true}),
emailCardSegments: feature('emailCardSegments', {developer: true}),
_user: null,

View File

@ -43,7 +43,6 @@ export default class UiService extends Service {
@tracked isFullScreen = false;
@tracked mainClass = '';
@tracked showMobileMenu = false;
@tracked showSettingsMenu = false;
get isMobile() {
return this.mediaQueries.isMobile;
@ -73,7 +72,6 @@ export default class UiService extends Service {
@action
closeMenus() {
this.dropdown.closeDropdowns();
this.showSettingsMenu = false;
this.showMobileMenu = false;
}
@ -87,11 +85,6 @@ export default class UiService extends Service {
this.showMobileMenu = true;
}
@action
openSettingsMenu() {
this.showSettingsMenu = true;
}
@action
setMainClass(mainClass) {
this.mainClass = mainClass;

View File

@ -435,20 +435,6 @@ input:focus,
color: var(--midgrey);
}
/* delete when psmRedesign flag is removed */
.gh-editor-header-small {
background: var(--dark-main-bg-color);
}
/* END delete */
/* psmRedesign labs feature */
@media (max-width: 1024px) {
.gh-editor-header.labs {
background-color: var(--dark-main-bg-color);
}
}
.gh-markdown-editor .CodeMirror-cursor {
border-color: #fff;
}
@ -505,7 +491,6 @@ input:focus,
background: var(--lightgrey);
}
.settings-menu-expanded .content-cover,
.mobile-menu-expanded .content-cover {
background: transparent;
}
@ -711,6 +696,12 @@ input:focus,
}
/* Editor */
@media (max-width: 1024px) {
.gh-editor-header {
background-color: var(--dark-main-bg-color);
}
}
.gh-editor-back-button {
background: var(--dark-main-bg-color);
}

View File

@ -23,6 +23,7 @@
position: relative;
z-index: 1000;
display: flex;
margin-right: 8px;
}
.gh-publishmenu-dropdown {

View File

@ -1,27 +1,69 @@
/* Settings Menu
/* ---------------------------------------------------------- */
/* Toggle button
/* ---------------------------------------------------------- */
.settings-menu-toggle {
position: absolute;
top: 31px;
right: 24px;
z-index: 9999;
margin-right: 0 !important;
}
.settings-menu-toggle svg {
fill: none !important;
}
.settings-menu-toggle .settings-menu-open svg path {
stroke: var(--black);
}
@media (min-width: 500px) and (max-width: 1024px) {
.settings-menu-toggle {
top: 12px;
}
}
@media (max-width: 500px) {
.settings-menu-toggle {
top: 15px;
}
}
.settings-menu-toggle-spacer {
width: 40px;
}
@media (max-width: 1024px) {
.settings-menu-toggle-spacer {
width: 68px;
}
}
/* Container
/* ---------------------------------------------------------- */
.settings-menu-container {
position: absolute;
top: 0;
right: calc(380px - 500px);
bottom: 0;
z-index: 500;
width: 500px;
max-width: 100%;
overflow: hidden;
background: var(--white);
transition: all 0.4s cubic-bezier(0.1, 0.7, 0.1, 1);
transform: translate3d(510px, 0px, 0px);
box-shadow: none;
z-index: 999;
height: 100vh;
min-width: 380px;
overflow-x: visible;
overflow-y: auto;
border-left: 1px solid var(--whitegrey-d1);
}
.settings-menu-expanded .settings-menu-container {
transform: translate3d(0, 0px, 0px);
.settings-menu-container-wide {
width: 501px;
min-width: 501px;
}
@media (max-width: 1024px) {
.settings-menu-container {
position: absolute;
right: 0;
box-shadow:
-4.5px 0 3.6px rgba(0, 0, 0, 0.007),
-12.5px 0 10px rgba(0, 0, 0, 0.008),
@ -29,56 +71,64 @@
-100px 0 80px rgba(0, 0, 0, 0.02)
;
}
}
@media (max-width: 800px) {
.settings-menu-container {
padding-bottom: 64px;
}
}
.settings-menu-container .settings-menu-pane {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
width: 100%;
max-width: 380px;
overflow: auto;
background-color: white;
opacity: 1;
width: 380px;
transform: translate3d(0, 0px, 0px);
-webkit-overflow-scrolling: touch;
}
.settings-menu-container .settings-menu-pane-wide {
.settings-menu-container-wide .settings-menu-pane {
width: 500px;
min-width: 500px;
}
.settings-menu-container-wide {
right: 0;
.settings-menu-container .settings-menu-pane-main {
position: relative;
top: auto;
right: auto;
bottom: auto;
height: 100vh;
}
@media (min-width: 901px) {
.settings-menu-container .settings-menu-pane {
transition: transform 0.4s cubic-bezier(0.1, 0.7, 0.1, 1);
}
}
.settings-menu-container .settings-menu-pane.settings-menu-pane-out-left {
transform: translate3d(-100%, 0px, 0px);
}
.settings-menu-container .settings-menu-pane.settings-menu-pane-out-right {
transform: translate3d(100%, 0px, 0px);
}
.settings-menu-container .settings-menu-pane.settings-menu-pane-in {
transform: translate3d(0, 0px, 0px);
}
/* Header
/* ---------------------------------------------------------- */
.settings-menu-header {
position: relative;
position: fixed;
display: flex;
width: 100%;
max-width: 364px;
padding: 36px 24px 24px;
justify-content: space-between;
align-items: center;
padding: 15px 24px;
z-index: 1;
background: var(--white);
}
@media (min-width: 500px) and (max-width: 1024px) {
.settings-menu-header {
padding-top: 17px;
}
}
@media (max-width: 500px) {
.settings-menu-header {
padding-top: 19px;
}
}
.settings-menu-header h4 {
@ -101,19 +151,26 @@
fill: var(--darkgrey);
}
.settings-menu-header.subview {
z-index: 2;
width: 100%;
max-width: 484px;
}
.settings-menu-header.subview h4 {
text-align: center;
width: 100%;
text-align: left;
}
.settings-menu-header.subview .back {
margin-left: -15px;
padding: 10px 15px;
padding: 2px 15px 0;
line-height: 14px;
}
.settings-menu-header.subview .back svg {
width: 14px;
height: 14px;
width: 12px;
height: 12px;
}
.settings-menu-header.subview .back svg path {
@ -138,7 +195,13 @@
}
.settings-menu-content {
padding: 0 24px 24px;
padding: 92px 24px 33px;
}
@media (max-width: 1024px) {
.settings-menu-content {
padding-top: 72px;
}
}
.settings-menu-content label code {
@ -333,7 +396,6 @@
}
}
.settings-menu-expanded .content-cover,
.mobile-menu-expanded .content-cover {
position: absolute;
top: 0;
@ -348,10 +410,6 @@
animation: coverFadeIn 0.3s ease 0.1s 1 forwards;
}
.settings-menu-expanded .content-cover {
transform: translate3d(-350px, 0px, 0px);
}
.mobile-menu-expanded .content-cover {
transform: translate3d(205px, 0px, 0px);
}
@ -376,169 +434,3 @@
.settings-menu-email .disabled input {
opacity: 0.5;
}
/* LABS ----------------------------------------------------------------------*/
.settings-menu-container-labs {
z-index: 999;
height: 100vh;
min-width: 380px;
overflow-x: visible;
overflow-y: auto;
border-left: 1px solid var(--whitegrey-d1);
}
.settings-menu-container-labs-wide {
width: 501px;
min-width: 501px;
}
@media (max-width: 1024px) {
.settings-menu-container-labs {
position: absolute;
right: 0;
box-shadow:
-4.5px 0 3.6px rgba(0, 0, 0, 0.007),
-12.5px 0 10px rgba(0, 0, 0, 0.008),
-30.1px 0 24.1px rgba(0, 0, 0, 0.01),
-100px 0 80px rgba(0, 0, 0, 0.02)
;
}
}
@media (max-width: 800px) {
.settings-menu-container-labs {
padding-bottom: 64px;
}
}
.settings-menu-toggle-labs {
position: absolute;
top: 31px;
right: 24px;
z-index: 9999;
margin-right: 0 !important;
}
.settings-menu-toggle-labs svg {
fill: none !important;
}
.settings-menu-toggle-labs .settings-menu-open svg path {
stroke: var(--black);
}
@media (min-width: 500px) and (max-width: 1024px) {
.settings-menu-toggle-labs {
top: 12px;
}
}
@media (max-width: 500px) {
.settings-menu-toggle-labs {
top: 15px;
}
}
.settings-menu-toggle-spacer-labs {
width: 40px;
}
@media (max-width: 1024px) {
.settings-menu-toggle-spacer-labs {
width: 68px;
}
}
.settings-menu-container-labs .settings-menu-pane {
position: absolute;
top: 0;
right: 0;
bottom: 0;
width: 100%;
max-width: 380px;
overflow: auto;
background-color: white;
opacity: 1;
}
.settings-menu-container-labs-wide .settings-menu-pane {
width: 500px;
min-width: 500px;
}
.settings-menu-container-labs .settings-menu-pane-main {
position: relative;
top: auto;
right: auto;
bottom: auto;
height: 100vh;
}
.settings-menu-header.labs {
position: fixed;
z-index: 1;
width: 100%;
max-width: 364px;
padding: 36px 24px 24px;
background: var(--white);
}
@media (min-width: 500px) and (max-width: 1024px) {
.settings-menu-header.labs {
padding-top: 17px;
}
}
@media (max-width: 500px) {
.settings-menu-header.labs {
padding-top: 19px;
}
}
.settings-menu-content.labs {
padding: 92px 24px 33px;
}
@media (max-width: 1024px) {
.settings-menu-content.labs {
padding-top: 72px;
}
}
.settings-menu-header.subview.labs {
z-index: 2;
width: 100%;
max-width: 484px;
}
.settings-menu-header.subview.labs .back {
padding: 2px 15px 0 !important;
}
.settings-menu-header.subview.labs .back svg {
width: 12px;
height: 12px;
}
.settings-menu-header.subview.labs h4 {
width: 100%;
text-align: left;
}
.gh-publishmenu-labs {
margin-right: 8px;
}
@media (max-width: 1024px) {
.gh-editor-header.labs {
z-index: 100;
height: 64px;
margin: 0;
padding: 0;
padding-left: 15px;
background-color: var(--white);
border-radius: 0;
}
}

View File

@ -318,6 +318,18 @@
z-index: 799;
}
@media (max-width: 1024px) {
.gh-editor-header {
z-index: 100;
height: 64px;
margin: 0;
padding: 0;
padding-left: 15px;
background-color: var(--white);
border-radius: 0;
}
}
.gh-btn-editor {
background: var(--white) !important;
}
@ -344,41 +356,6 @@
background: transparent;
}
/* delete when psmRedesign flag is removed */
@media (max-width: 750px) {
.gh-editor-header {
padding: 0 4vw;
}
}
.gh-editor-header-small {
z-index: 100;
height: 48px;
padding: 0;
padding-left: 15px;
background-color: #fff;
margin: 0;
border-radius: 0;
}
.gh-editor-header-small .gh-publishmenu {
line-height: 0;
}
.gh-editor-header-small .post-settings {
padding: 13px 15px;
}
.gh-editor-header-small .gh-koenig-info {
position: absolute;
top: 60px;
left: 0;
right: 0;
margin: 0 auto;
padding: 0 8px;
}
/* END delete when psmRedesign flag is removed */
.gh-editor-status {
color: var(--midgrey);
font-size: 1.3rem;

View File

@ -1,9 +1,9 @@
<GhApp @showSettingsMenu={{this.ui.showSettingsMenu}}>
<GhApp>
<GhSkipLink @anchor=".gh-main">Skip to main content</GhSkipLink>
<GhAlerts />
<div class="gh-viewport {{if this.ui.showSettingsMenu 'settings-menu-expanded'}} {{if this.ui.showMobileMenu 'mobile-menu-expanded'}}">
<div class="gh-viewport {{if this.ui.showMobileMenu 'mobile-menu-expanded'}}">
{{#if this.showNavMenu}}
<GhNavMenu />
{{/if}}

View File

@ -5,7 +5,7 @@
@class="gh-editor gh-view relative"
as |editor|
>
<header class="gh-editor-header br2 pe-none {{editor.headerClass}} {{if (feature "psmRedesign") "labs"}}">
<header class="gh-editor-header br2 pe-none">
<div class="flex items-center pe-auto">
{{#if this.ui.isFullScreen}}
<div class="ml3 flex items-center">
@ -43,41 +43,19 @@
@runningText="Saving"
@class="gh-btn gh-btn-blue gh-btn-icon contributor-save-button"
data-test-contributor-save=true />
{{else if (feature "psmRedesign")}}
<div class="gh-publishmenu-labs">
<GhPublishmenu
@post={{this.post}}
@postStatus={{this.post.status}}
@saveTask={{this.saveTask}}
@setSaveType={{action "setSaveType"}}
@onOpen={{action "cancelAutosave"}} />
</div>
{{else}}
<div>
<GhPublishmenu
@post={{this.post}}
@postStatus={{this.post.status}}
@saveTask={{this.saveTask}}
@setSaveType={{action "setSaveType"}}
@onOpen={{action "cancelAutosave"}} />
</div>
{{/if}}
{{#if (feature "psmRedesign")}}
{{#unless this.showSettingsMenu}}
<div class="settings-menu-toggle-spacer-labs"></div>
<div class="settings-menu-toggle-spacer"></div>
{{/unless}}
{{/if}}
{{/unless}}
<div>
{{#unless (feature "psmRedesign")}}
{{!-- TODO: clean up everything related to ui.showSettingsMenu when flag is removed --}}
<button type="button" class="gh-btn gh-btn-editor gh-btn-icon only-has-icon gh-actions-cog ml3" title="Settings" data-test-button="psm-toggle" {{action "openSettingsMenu" target=this.ui}} data-test-psm-trigger>
<span>{{svg-jar "settings"}}</span>
</button>
{{/unless}}
</div>
</section>
</header>
@ -96,7 +74,6 @@
@onBodyChange={{action "updateScratch"}}
@headerOffset={{editor.headerHeight}}
@scrollContainerSelector=".gh-koenig-editor"
@scrollOffsetTopSelector=".gh-editor-header-small"
@scrollOffsetBottomSelector=".gh-mobile-nav-bar"
@onEditorCreated={{action "setKoenigEditor"}}
@onWordCountChange={{action "updateWordCount"}}
@ -112,18 +89,17 @@
@clearFeatureImage={{action "clearFeatureImage"}}
/>
<div class="gh-editor-wordcount-container {{if editor.headerClass "small"}}">
<div class="midgrey-l2 {{if editor.headerClass "f-supersmall pl2 pr2" "f8 pl4 pr3"}} fw4">
<div class="gh-editor-wordcount-container">
<div class="midgrey-l2 f8 pl4 pr3 fw4">
{{gh-pluralize this.wordCount.wordCount "word"}}
</div>
<a href="https://ghost.org/help/using-the-editor/" class="flex {{if editor.headerClass "pa2" "pa3"}}" target="_blank">{{svg-jar "help" class="w4 h4 stroke-midgrey-l2"}}</a>
<a href="https://ghost.org/help/using-the-editor/" class="flex pa3" target="_blank">{{svg-jar "help" class="w4 h4 stroke-midgrey-l2"}}</a>
</div>
</GhEditor>
{{#if (feature "psmRedesign")}}
{{#if this.showSettingsMenu}}
<GhPostSettingsMenuLabs
<GhPostSettingsMenu
@post={{this.post}}
@showSettingsMenu={{this.showSettingsMenu}}
@toggleEmailPreviewModal={{action "toggleEmailPreviewModal"}}
@ -132,19 +108,15 @@
@savePostTask={{this.savePostTask}}
/>
{{/if}}
{{/if}}
</div>
{{#if (feature "psmRedesign")}}
<button type="button" class="settings-menu-toggle-labs gh-btn gh-btn-editor gh-btn-icon only-has-icon gh-actions-cog" title="Settings" data-test-button="psm-toggle" {{on "click" this.toggleSettingsMenu}} data-test-psm-trigger>
<button type="button" class="settings-menu-toggle gh-btn gh-btn-editor gh-btn-icon only-has-icon gh-actions-cog" title="Settings" data-test-button="psm-toggle" {{on "click" this.toggleSettingsMenu}} data-test-psm-trigger>
{{#if this.showSettingsMenu}}
{{!-- TODO: switch to close state --}}
<span class="settings-menu-open">{{svg-jar "sidemenu-open"}}</span>
{{else}}
<span>{{svg-jar "sidemenu"}}</span>
{{/if}}
</button>
{{/if}}
{{#if this.showDeletePostModal}}
<GhFullscreenModal @modal="delete-post"
@ -206,19 +178,6 @@
@modifier="action wide"
/>
{{/if}}
{{#unless (feature "psmRedesign")}}
<LiquidWormhole>
<GhPostSettingsMenu
@post={{this.post}}
@showSettingsMenu={{this.ui.showSettingsMenu}}
@toggleEmailPreviewModal={{action "toggleEmailPreviewModal"}}
@deletePost={{action "toggleDeletePostModal"}}
@updateSlugTask={{this.updateSlugTask}}
@savePostTask={{this.savePostTask}}
/>
</LiquidWormhole>
{{/unless}}
{{/if}}
{{outlet}}

View File

@ -267,19 +267,6 @@
</div>
</div>
</div>
<div class="gh-expandable-block">
<div class="gh-expandable-header">
<div>
<h4 class="gh-expandable-title">Post settings menu behaviour change</h4>
<p class="gh-expandable-description">
Switches post settings menu behaviour from a popover to a collapsible sidebar.
</p>
</div>
<div class="for-switch">
<GhFeatureFlag @flag="psmRedesign" />
</div>
</div>
</div>
<div class="gh-expandable-block">
<div class="gh-expandable-header">
<div>