From dd4fe3a68f6d40ccfabbb231fb06081f8b03cd5d Mon Sep 17 00:00:00 2001 From: Aileen Nowak Date: Thu, 3 Aug 2017 15:45:14 +0400 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20=20Added=20Twitter=20&=20Facebook?= =?UTF-8?q?=20data=20override=20fields=20to=20PSM=20(#814)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit refs https://github.com/TryGhost/Ghost/issues/8334, requires https://github.com/TryGhost/Ghost/pull/8827 - added open graph and twitter fields to `Post` model - added facebook and twitter card pane to PSM - 💅🏼 Added preview styles for custom FB/Twitter cards --- .../app/components/gh-post-settings-menu.js | 191 +++++++++++--- .../app/mixins/editor-base-controller.js | 4 + ghost/admin/app/models/post.js | 10 + ghost/admin/app/services/config.js | 9 + .../app/styles/components/settings-menu.css | 31 ++- ghost/admin/app/styles/layouts/editor.css | 242 +++++++++++++----- ghost/admin/app/styles/patterns/forms.css | 39 +-- .../components/gh-post-settings-menu.hbs | 169 +++++++++++- ghost/admin/app/validators/post.js | 50 +++- ghost/admin/package.json | 1 + ghost/admin/tests/acceptance/editor-test.js | 122 +++++++++ ghost/admin/yarn.lock | 102 +------- 12 files changed, 749 insertions(+), 221 deletions(-) diff --git a/ghost/admin/app/components/gh-post-settings-menu.js b/ghost/admin/app/components/gh-post-settings-menu.js index 6d11fe379e..d647278680 100644 --- a/ghost/admin/app/components/gh-post-settings-menu.js +++ b/ghost/admin/app/components/gh-post-settings-menu.js @@ -1,19 +1,15 @@ import Component from 'ember-component'; -import Ember from 'ember'; import SettingsMenuMixin from 'ghost-admin/mixins/settings-menu-component'; import boundOneWay from 'ghost-admin/utils/bound-one-way'; -import computed, {alias} from 'ember-computed'; +import computed, {alias, or} from 'ember-computed'; import formatMarkdown from 'ghost-admin/utils/format-markdown'; import injectService from 'ember-service/inject'; import moment from 'moment'; import run from 'ember-runloop'; import {guidFor} from 'ember-metal/utils'; -import {htmlSafe} from 'ember-string'; import {invokeAction} from 'ember-invoke-action'; import {task, timeout} from 'ember-concurrency'; -const {Handlebars} = Ember; - const PSM_ANIMATION_LENGTH = 400; export default Component.extend(SettingsMenuMixin, { @@ -35,8 +31,20 @@ export default Component.extend(SettingsMenuMixin, { codeinjectionHeadScratch: alias('model.codeinjectionHeadScratch'), metaDescriptionScratch: alias('model.metaDescriptionScratch'), metaTitleScratch: alias('model.metaTitleScratch'), + ogDescriptionScratch: alias('model.ogDescriptionScratch'), + ogTitleScratch: alias('model.ogTitleScratch'), + twitterDescriptionScratch: alias('model.twitterDescriptionScratch'), + twitterTitleScratch: alias('model.twitterTitleScratch'), slugValue: boundOneWay('model.slug'), + seoTitle: or('metaTitleScratch', 'model.titleScratch'), + twitterImage: or('model.twitterImage', 'model.featureImage'), + twitterTitle: or('twitterTitleScratch', 'seoTitle'), + twitterDescription: or('twitterDescriptionScratch', 'customExcerptScratch', 'seoDescription'), + facebookImage: or('model.ogImage', 'model.featureImage'), + facebookTitle: or('ogTitleScratch', 'seoTitle'), + facebookDescription: or('ogDescriptionScratch', 'customExcerptScratch', 'seoDescription'), + _showSettingsMenu: false, _showThrobbers: false, @@ -84,27 +92,13 @@ export default Component.extend(SettingsMenuMixin, { this.set('_showThrobbers', true); }).restartable(), - seoTitle: computed('model.titleScratch', 'metaTitleScratch', function () { - let metaTitle = this.get('metaTitleScratch') || ''; - - metaTitle = metaTitle.length > 0 ? metaTitle : this.get('model.titleScratch'); - - if (metaTitle.length > 70) { - metaTitle = metaTitle.substring(0, 70).trim(); - metaTitle = Handlebars.Utils.escapeExpression(metaTitle); - metaTitle = htmlSafe(`${metaTitle}…`); - } - - return metaTitle; - }), - seoDescription: computed('model.scratch', 'metaDescriptionScratch', function () { let metaDescription = this.get('metaDescriptionScratch') || ''; let mobiledoc = this.get('model.scratch'); let markdown = mobiledoc.cards && mobiledoc.cards[0][1].markdown; let placeholder; - if (metaDescription.length > 0) { + if (metaDescription) { placeholder = metaDescription; } else { let div = document.createElement('div'); @@ -116,13 +110,6 @@ export default Component.extend(SettingsMenuMixin, { placeholder = placeholder.replace(/\n+/g, ' ').trim(); } - if (placeholder.length > 156) { - // Limit to 156 characters - placeholder = placeholder.substring(0, 156).trim(); - placeholder = Handlebars.Utils.escapeExpression(placeholder); - placeholder = htmlSafe(`${placeholder}…`); - } - return placeholder; }), @@ -136,12 +123,6 @@ export default Component.extend(SettingsMenuMixin, { seoURL += '/'; } - if (seoURL.length > 70) { - seoURL = seoURL.substring(0, 70).trim(); - seoURL = Handlebars.Utils.escapeExpression(seoURL); - seoURL = htmlSafe(`${seoURL}…`); - } - return seoURL; }), @@ -344,6 +325,98 @@ export default Component.extend(SettingsMenuMixin, { }); }, + setOgTitle(ogTitle) { + // Grab the model and current stored facebook title + let model = this.get('model'); + let currentTitle = model.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 + model.set('ogTitle', ogTitle); + + // Make sure the facebook title is valid and if so, save it into the model + return model.validate({property: 'ogTitle'}).then(() => { + if (model.get('isNew')) { + return; + } + + return model.save(); + }); + }, + + setOgDescription(ogDescription) { + // Grab the model and current stored facebook description + let model = this.get('model'); + let currentDescription = model.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 + model.set('ogDescription', ogDescription); + + // Make sure the facebook description is valid and if so, save it into the model + return model.validate({property: 'ogDescription'}).then(() => { + if (model.get('isNew')) { + return; + } + + return model.save(); + }); + }, + + setTwitterTitle(twitterTitle) { + // Grab the model and current stored twitter title + let model = this.get('model'); + let currentTitle = model.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 + model.set('twitterTitle', twitterTitle); + + // Make sure the twitter title is valid and if so, save it into the model + return model.validate({property: 'twitterTitle'}).then(() => { + if (model.get('isNew')) { + return; + } + + return model.save(); + }); + }, + + setTwitterDescription(twitterDescription) { + // Grab the model and current stored twitter description + let model = this.get('model'); + let currentDescription = model.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 + model.set('twitterDescription', twitterDescription); + + // Make sure the twitter description is valid and if so, save it into the model + return model.validate({property: 'twitterDescription'}).then(() => { + if (model.get('isNew')) { + return; + } + + return model.save(); + }); + }, + setCoverImage(image) { this.set('model.featureImage', image); @@ -370,6 +443,58 @@ export default Component.extend(SettingsMenuMixin, { }); }, + setOgImage(image) { + this.set('model.ogImage', image); + + if (this.get('model.isNew')) { + return; + } + + this.get('model').save().catch((error) => { + this.showError(error); + this.get('model').rollbackAttributes(); + }); + }, + + clearOgImage() { + this.set('model.ogImage', ''); + + if (this.get('model.isNew')) { + return; + } + + this.get('model').save().catch((error) => { + this.showError(error); + this.get('model').rollbackAttributes(); + }); + }, + + setTwitterImage(image) { + this.set('model.twitterImage', image); + + if (this.get('model.isNew')) { + return; + } + + this.get('model').save().catch((error) => { + this.showError(error); + this.get('model').rollbackAttributes(); + }); + }, + + clearTwitterImage() { + this.set('model.twitterImage', ''); + + if (this.get('model.isNew')) { + return; + } + + this.get('model').save().catch((error) => { + this.showError(error); + this.get('model').rollbackAttributes(); + }); + }, + closeNavMenu() { invokeAction(this, 'closeNavMenu'); }, diff --git a/ghost/admin/app/mixins/editor-base-controller.js b/ghost/admin/app/mixins/editor-base-controller.js index ccfcc67dbf..5a692334ea 100644 --- a/ghost/admin/app/mixins/editor-base-controller.js +++ b/ghost/admin/app/mixins/editor-base-controller.js @@ -163,6 +163,10 @@ export default Mixin.create({ this.set('model.headerInjection', this.get('model.headerExcerptScratch')); this.set('model.metaTitle', this.get('model.metaTitleScratch')); this.set('model.metaDescription', this.get('model.metaDescriptionScratch')); + this.set('model.ogTitle', this.get('model.ogTitleScratch')); + this.set('model.ogDescription', this.get('model.ogDescriptionScratch')); + this.set('model.twitterTitle', this.get('model.twitterTitleScratch')); + this.set('model.twitterDescription', this.get('model.twitterDescriptionScratch')); if (!this.get('model.slug')) { this.get('saveTitle').cancelAll(); diff --git a/ghost/admin/app/models/post.js b/ghost/admin/app/models/post.js index 0625bdef9c..70754c4334 100644 --- a/ghost/admin/app/models/post.js +++ b/ghost/admin/app/models/post.js @@ -82,6 +82,12 @@ export default Model.extend(Comparable, ValidationEngine, { featureImage: attr('string'), codeinjectionFoot: attr('string', {defaultValue: ''}), codeinjectionHead: attr('string', {defaultValue: ''}), + ogImage: attr('string'), + ogTitle: attr('string'), + ogDescription: attr('string'), + twitterImage: attr('string'), + twitterTitle: attr('string'), + twitterDescription: attr('string'), html: attr('string'), locale: attr('string'), metaDescription: attr('string'), @@ -122,6 +128,10 @@ export default Model.extend(Comparable, ValidationEngine, { codeinjectionHeadScratch: boundOneWay('codeinjectionHead'), metaDescriptionScratch: boundOneWay('metaDescription'), metaTitleScratch: boundOneWay('metaTitle'), + ogDescriptionScratch: boundOneWay('ogDescription'), + ogTitleScratch: boundOneWay('ogTitle'), + twitterDescriptionScratch: boundOneWay('twitterDescription'), + twitterTitleScratch: boundOneWay('twitterTitle'), isPublished: equal('status', 'published'), isDraft: equal('status', 'draft'), diff --git a/ghost/admin/app/services/config.js b/ghost/admin/app/services/config.js index 4e3a7fefbc..b8cf4214d0 100644 --- a/ghost/admin/app/services/config.js +++ b/ghost/admin/app/services/config.js @@ -39,5 +39,14 @@ export default Service.extend(_ProxyMixin, { ghostOAuth: computed('ghostAuthId', function () { return !isBlank(this.get('ghostAuthId')); + }), + + blogDomain: computed('blogUrl', function () { + let blogUrl = this.get('blogUrl'); + let blogDomain = blogUrl + .replace(/^https?:\/\//, '') + .replace(/\/?$/, ''); + + return blogDomain; }) }); diff --git a/ghost/admin/app/styles/components/settings-menu.css b/ghost/admin/app/styles/components/settings-menu.css index db4515cc9c..2dfe06d72a 100644 --- a/ghost/admin/app/styles/components/settings-menu.css +++ b/ghost/admin/app/styles/components/settings-menu.css @@ -11,9 +11,9 @@ right: 0; bottom: 0; z-index: 500; - overflow: hidden; - max-width: 100%; width: 350px; + max-width: 100%; + overflow: hidden; background: color(var(--lightgrey) l(+4%)); transition: transform 0.4s cubic-bezier(0.1, 0.7, 0.1, 1); transform: translate3d(350px, 0px, 0px); @@ -30,9 +30,9 @@ bottom: 0; left: 0; overflow: auto; - -webkit-overflow-scrolling: touch; opacity: 1; transform: translate3d(0, 0px, 0px); + -webkit-overflow-scrolling: touch; } @media (min-width: 901px) { @@ -80,8 +80,8 @@ } .settings-menu-header .close svg { - height: 12px; width: 12px; + height: 12px; fill: var(--darkgrey); } @@ -96,8 +96,8 @@ } .settings-menu-header.subview .back svg { - height: 14px; width: 14px; + height: 14px; } .settings-menu-header.subview .back svg path { @@ -128,16 +128,21 @@ } .settings-menu-content .gh-image-uploader.-with-image { - margin-top: 0; + width: auto; min-height: 50px; max-height: 250px; - width: auto; + margin-top: 0; } .settings-menu-content textarea { height: 108px; } +.settings-menu-content textarea.gh-input { + font-size: 1.5rem; + line-height: 1.3em; +} + .settings-menu-content .nav-list { margin-top: 3rem; } @@ -147,9 +152,9 @@ } .ghost-url-preview { + width: 98%; /* Preview never wider than input */ overflow: hidden; - width: 98%; text-overflow: ellipsis; white-space: nowrap; } @@ -171,16 +176,16 @@ } .settings-menu-delete-button svg path { - fill: var(--red); stroke: var(--red); + fill: var(--red); stroke-width: 1px; } .settings-menu-delete-button:hover, .settings-menu-delete-button:hover svg path { - color: color(var(--red) lightness(-10%)); - fill: color(var(--red) lightness(-10%)); stroke: color(var(--red) lightness(-10%)); + fill: color(var(--red) lightness(-10%)); + color: color(var(--red) lightness(-10%)); } .settings-menu-content .selectize-input { @@ -198,6 +203,10 @@ padding: 0; } +.settings-menu-content .CodeMirror:hover { + cursor: text; +} + .settings-menu-content .CodeMirror-scroll { min-height: 170px; } diff --git a/ghost/admin/app/styles/layouts/editor.css b/ghost/admin/app/styles/layouts/editor.css index 626d1fc7b1..bb6a857674 100644 --- a/ghost/admin/app/styles/layouts/editor.css +++ b/ghost/admin/app/styles/layouts/editor.css @@ -25,16 +25,16 @@ .label-tag { margin-right: 0.3em; padding: 0.2em 0.6em 0.3em; + color: var(--lightgrey); + font-weight: 300; + text-align: center; background-color: var(--darkgrey); border-radius: 0.25em; - color: var(--lightgrey); - text-align: center; - font-weight: 300; } .label-tag.highlight { - background: var(--midgrey); color: #fff; + background: var(--midgrey); } .tag-input { @@ -57,12 +57,12 @@ .post-settings { position: relative; + z-index: 1000; display: inline-block; padding: 15px 0 15px 15px; color: var(--midgrey); - transition: all 0.15s ease-out 0s; line-height: 0; - z-index: 1000; + transition: all 0.15s ease-out 0s; } .post-settings:hover, @@ -97,8 +97,8 @@ .post-view-link svg { display: inline; - height: 10px; width: 10px; + height: 10px; fill: var(--blue); } @@ -106,17 +106,19 @@ /* Post settings meta /* ---------------------------------------------------------- */ -/* Google Imitation */ +/* Google SERP Preview */ .seo-preview { font-family: Arial, sans-serif; + background: #fff; + padding: 10px 12px; } .seo-preview-title { color: #1e0fbe; - text-overflow: ellipses; - word-wrap: break-word; font-size: 1.8rem; line-height: 2.16rem; + text-overflow: ellipses; + word-wrap: break-word; -webkit-text-overflow: ellipsis; } @@ -124,18 +126,141 @@ .seo-preview-link { margin: 1px 0 2px 0; color: #006621; - word-wrap: break-word; font-size: 1.3rem; line-height: 1.6rem; + word-wrap: break-word; } .seo-preview-description { color: #545454; - word-wrap: break-word; font-size: 1.3rem; line-height: 1.4; + word-wrap: break-word; } +/* Facebook Card Preview */ +.gh-og-preview { + background: #fff; + box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.1) inset, + 0 -1px 0 0 rgba(0, 0, 0, 0.06) inset, + 0 1px 4px rgba(0, 0, 0, 0.1); +} + +.gh-og-preview-image { + width: 100%; + height: 160px; + background-size: cover; + background-position: center center; +} + +.gh-og-preview-content { + padding: 10px 12px; +} + +.gh-og-preview-title { + max-height: 110px; + overflow: hidden; + margin-bottom: 5px; + font-family: Georgia, serif; + font-size: 18px; + line-height: 22px; + font-weight: 500; + word-wrap: break-word; +} + +.gh-og-preview-description { + max-height: 80px; + overflow: hidden; + font-size: 12px; + line-height: 16px; + letter-spacing: -0.1px; +} + +.gh-og-preview-footer { + display: flex; + justify-content: space-between; + align-items: center; +} + +.gh-og-preview-footer-left { + overflow: hidden; + padding-top: 18px; + color: #90949c; + font-size: 11px; + line-height: 11px; + letter-spacing: -0.1px; + text-transform: uppercase; + text-overflow: ellipsis; + white-space: nowrap; +} + +.gh-og-preview-footer-left-divider { + padding: 0 5px; +} + +.gh-og-preview-footer-author { + color: #3b5998; +} + + +/* Twitter Card Preview */ +.gh-twitter-preview { + overflow: hidden; + border-width: 1px; + border-style: solid; + border-color: #e1e8ed; + color: #292f33; + font-family: "Helvetica Neue",Helvetica,Arial,sans-serif; + font-size: 1.4rem; + line-height: 1.3em; + background: #fff; + border-radius: 0.42857em; + + -webkit-font-smoothing: antialiased; +} + +.gh-twitter-preview-image { + width: 100%; + height: 160px; + background-size: cover; + background-position: center center; +} + +.gh-twitter-preview-content { + padding: 12px 14px; +} + +.gh-twitter-preview-title { + max-height: 1.3em; + overflow: hidden; + margin: 0 0 0.15em; + font-weight: bold; + text-overflow: ellipsis; + white-space: nowrap; +} + +.gh-twitter-preview-description { + overflow: hidden; + margin-top: 0.32333em; +} + +.gh-twitter-preview-footer { + display: flex; + justify-content: space-between; + align-items: center; + margin-top: 0.35em; +} + +.gh-twitter-preview-footer-left { + max-height: 1.3em; + overflow: hidden; + color: #8899a6; + text-transform: lowercase; + text-overflow: ellipsis; + white-space: nowrap; +} + + /* NEW editor @@ -145,9 +270,9 @@ position: fixed; top: 0; left: 0; + z-index: 1000; width: 100%; height: 100%; - z-index: 1000; background-color: white; } @@ -159,20 +284,22 @@ display: flex; justify-content: space-between; align-items: center; - padding: 0 30px; height: 80px; + padding: 0 30px; } @media (max-width: 750px) { - .gh-editor-header { padding: 0 4vw; } + .gh-editor-header { + padding: 0 4vw; + } } .gh-editor-header-small { + z-index: 100; height: 43px; padding: 0; padding-left: 15px; - background-color: #fff; border-bottom: var(--lightgrey) 1px solid; - z-index: 100; + background-color: #fff; } .gh-editor-header-small .gh-publishmenu { @@ -185,23 +312,23 @@ .gh-editor-status { color: var(--midgrey); - font-weight: 300; font-size: 1.3rem; + font-weight: 300; } .gh-editor-container { position: absolute; - overflow-y: auto; - padding: 10vw 4vw; + z-index: 0; width: 100%; height: 100%; - z-index: 0; + overflow-y: auto; + padding: 10vw 4vw; } .gh-editor-inner { - margin: 0 auto; - max-width: 760px; height: 100%; + max-width: 760px; + margin: 0 auto; } .gh-editor-title { @@ -210,19 +337,18 @@ min-height: auto; margin-bottom: 2vw; border: none; - letter-spacing: 0.8px; - font-weight: bold; + color: var(--darkgrey); font-size: 3.2rem; line-height: 1.15em; - color: var(--darkgrey); - + font-weight: bold; + letter-spacing: 0.8px; } .gh-editor-wordcount { position: fixed; - bottom: 0px; - padding:10px; - } + bottom: 0; + padding: 10px; +} @media (max-width: 1200px) { .gh-editor-wordcount { @@ -243,10 +369,10 @@ .gh-markdown-editor { position: relative; + z-index: 0; width: 100%; height: 100vh; overflow-y: auto; - z-index: 0; } .gh-markdown-editor-pane, @@ -282,23 +408,25 @@ } .gh-editor-footer { - height: 46px; - min-height: 46px; display: flex; flex-direction: row; justify-content: space-between; align-items: center; + height: 46px; + min-height: 46px; border-top: var(--lightgrey) 1px solid; } @media (max-width: 800px) { - .gh-editor-footer { display: none; } + .gh-editor-footer { + display: none; + } } .gh-editor-footer .editor-toolbar { border: none; - border-radius: 0; background: transparent; + border-radius: 0; opacity: 1; } @@ -312,14 +440,14 @@ } .gh-editor .CodeMirror { - padding: 0; overflow: visible; + padding: 0; background: transparent; } .gh-editor .CodeMirror-cursor { - border-color: var(--blue); border-width: 3px; + border-color: var(--blue); } /* fix visible scrollbars when OSX is set to show them */ @@ -339,17 +467,17 @@ .gh-editor .gh-editor-title, .gh-editor .CodeMirror-wrap { max-width: 760px; - margin-left: auto; margin-right: auto; + margin-left: auto; border: none; background: transparent; } .gh-editor .CodeMirror pre { padding: 0; + color: color(var(--darkgrey) l(+5%)); font-family: “Consolas”, monaco, monospace; font-size: 1.6rem; - color: color(var(--darkgrey) l(+5%)); } @media (max-width: 960px) { @@ -375,9 +503,9 @@ .gh-editor .editor-preview, .gh-markdown-editor-preview { + color: color(var(--darkgrey) l(+5%)); font-family: Georgia, Times, Serif; font-size: 1.9rem; - color: color(var(--darkgrey) l(+5%)); } .gh-editor .editor-preview h1, @@ -392,7 +520,7 @@ .gh-markdown-editor-preview h5, .gh-editor .editor-preview h6, .gh-markdown-editor-preview h6 { - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif; } .gh-markdown-editor-preview-title { @@ -401,17 +529,17 @@ .gh-editor-drop-target, .gh-editor-image-upload { + content: ""; position: absolute; top: 0; left: 0; + z-index: 9999; + display: flex; + justify-content: center; + align-items: center; width: 100%; height: 100%; - display: flex; - align-items: center; - justify-content: center; border: 2px solid var(--blue); - content: ''; - z-index: 9999; background-color: rgba(255,255,255,0.6); } @@ -427,13 +555,13 @@ .gh-editor-image-upload-content { display: flex; - align-items: center; - justify-content: center; flex-direction: column; + justify-content: center; + align-items: center; + max-width: 80%; padding: 1em; background-color: #fff; border-radius: 1em; - max-width: 80%; } .gh-editor-image-upload .gh-progress-container-progress { @@ -451,8 +579,8 @@ .editor-toolbar a.disabled, .gh-editor-preview .editor-toolbar a:not(.no-disable) { - pointer-events: none; color: var(--lightgrey) !important; + pointer-events: none; } .editor-toolbar a.disabled:hover { @@ -464,20 +592,20 @@ vertical-align: bottom; } .editor-toolbar .fa-check:before { - font-size: 14px; position: absolute; - line-height: 14px; right: 3px; bottom: 4px; + font-size: 14px; + line-height: 14px; } .editor-toolbar .fa-check:after { - content: 'abc'; - font-family: var(--font-family); + content: "abc"; position: absolute; - left: 4px; - font-size: 9px; top: 6px; + left: 4px; + font-family: var(--font-family); + font-size: 9px; line-height: 9px; } @@ -496,8 +624,8 @@ } .editor-toolbar i.separator { - border-left: color(var(--lightgrey) l(-3%)) 1px solid; border-right: none; + border-left: color(var(--lightgrey) l(-3%)) 1px solid; } .editor-toolbar a.active, diff --git a/ghost/admin/app/styles/patterns/forms.css b/ghost/admin/app/styles/patterns/forms.css index 67975db33a..5d1aea48ab 100644 --- a/ghost/admin/app/styles/patterns/forms.css +++ b/ghost/admin/app/styles/patterns/forms.css @@ -23,8 +23,8 @@ fieldset { legend { display: block; - margin: 2em 0; width: 100%; + margin: 2em 0; border-bottom: color(var(--lightgrey) l(-5%) s(-10%)) 1px solid; color: var(--midgrey); font-size: 1.2em; @@ -56,9 +56,9 @@ input { .form-group { position: relative; - margin-bottom: 1.6em; - max-width: 700px; width: 100%; + max-width: 700px; + margin-bottom: 1.6em; user-select: text; } @@ -97,8 +97,8 @@ input { top: 50%; left: 10px; z-index: 2; - height: 14px; width: 14px; + height: 14px; fill: color(var(--midgrey) l(+15%)); transform: translateY(-7px); } @@ -122,16 +122,16 @@ input { .gh-select, select { display: block; - padding: 10px 12px; width: 100%; height: 40px; + padding: 10px 12px; border: color(var(--lightgrey) l(-5%) s(-10%)) 1px solid; - border-radius: var(--border-radius); color: color(var(--midgrey) l(-18%)); font-size: 1.6rem; line-height: 1em; font-weight: 300; user-select: text; + border-radius: var(--border-radius); transition: border-color 0.15s linear; -webkit-appearance: none; @@ -158,11 +158,11 @@ select:focus { } textarea { + width: 100%; + height: auto; min-width: 250px; min-height: 10rem; max-width: 500px; - width: 100%; - height: auto; line-height: 1.5; user-select: text; resize: vertical; @@ -192,7 +192,7 @@ textarea { top: 0; right: 0; bottom: 0; - display:none; + display: none; } .for-radio .input-toggle-component, @@ -201,9 +201,9 @@ textarea { top: 1px; display: inline-block; float: left; - margin-right: 7px; width: 18px; height: 18px; + margin-right: 7px; border: 1px solid color(var(--lightgrey) l(-5%) s(-10%)); background: #fff; } @@ -282,23 +282,24 @@ textarea { .gh-select { position: relative; display: block; - padding: 0; - max-width: 100%; width: 100%; + max-width: 100%; + padding: 0; border-width: 0; } .gh-select svg { - height: 8px; - width: 14px; position: absolute; top: 50%; right: 1.2rem; left: inherit; + width: 14px; + height: 8px; margin-top: -0.2em; - pointer-events: none; - speak: none; transform: inherit; + pointer-events: none; + + speak: none; } .gh-select svg path { @@ -308,10 +309,10 @@ textarea { .gh-select select { padding: 10px 12px; outline: none; - background: #fff; + line-height: normal; text-indent: 0.01px; text-overflow: ""; - line-height: normal; + background: #fff; appearance: none; -webkit-appearance: none; @@ -332,9 +333,9 @@ textarea { /* ---------------------------------------------------------- */ .gh-input-file { - font-size: 1.2rem; width: auto; height: auto; + font-size: 1.2rem; } .gh-input-file + .gh-btn { diff --git a/ghost/admin/app/templates/components/gh-post-settings-menu.hbs b/ghost/admin/app/templates/components/gh-post-settings-menu.hbs index 246a4e1e9b..1236330c06 100644 --- a/ghost/admin/app/templates/components/gh-post-settings-menu.hbs +++ b/ghost/admin/app/templates/components/gh-post-settings-menu.hbs @@ -117,14 +117,28 @@ + + @@ -172,9 +186,10 @@ name="post-setting-meta-title" focusOut=(action "setMetaTitle" metaTitleScratch) stopEnterKeyDownPropagation="true" - update=(action (mut metaTitleScratch))}} + update=(action (mut metaTitleScratch)) + data-test-field="meta-title"}}

Recommended: 70 characters. You’ve used {{gh-count-down-characters metaTitleScratch 70}}

- {{gh-error-message errors=model.errors property="metaTitle"}} + {{gh-error-message errors=model.errors property="meta-title"}} {{/gh-form-group}} {{#gh-form-group errors=model.errors hasValidated=model.hasValidated property="metaDescription"}} @@ -185,23 +200,159 @@ name="post-setting-meta-description" focusOut=(action "setMetaDescription" metaDescriptionScratch) stopEnterKeyDownPropagation="true" - update=(action (mut metaDescriptionScratch))}} + update=(action (mut metaDescriptionScratch)) + data-test-field="meta-description"}}

Recommended: 156 characters. You’ve used {{gh-count-down-characters metaDescriptionScratch 156}}

- {{gh-error-message errors=model.errors property="metaDescription"}} + {{gh-error-message errors=model.errors property="meta-description"}} {{/gh-form-group}}
-
{{seoTitle}}
- -
{{seoDescription}}
+
{{truncate seoTitle 70}}
+ +
{{truncate seoDescription 300}}
{{/if}} + {{#if (eq subview "twitter-data")}} +
+ +

Twitter Card

+
+
+ +
+ +
+ {{gh-image-uploader-with-preview + image=model.twitterImage + text="Add Twitter image" + update=(action "setTwitterImage") + remove=(action "clearTwitterImage") + }} + {{#gh-form-group errors=model.errors hasValidated=model.hasValidated property="twitterTitle"}} + + {{gh-input twitterTitleScratch + class="post-setting-twitter-title" + id="twitter-title" + name="post-setting-twitter-title" + placeholder=(truncate twitterTitle 40) + focusOut=(action "setTwitterTitle" twitterTitleScratch) + stopEnterKeyDownPropagation="true" + update=(action (mut twitterTitleScratch)) + data-test-field="twitter-title"}} + {{gh-error-message errors=model.errors property="twitterTitle" data-test-error="twitter-title"}} + {{/gh-form-group}} + + {{#gh-form-group errors=model.errors hasValidated=model.hasValidated property="twitterDescription"}} + + {{gh-textarea twitterDescriptionScratch + class="post-setting-twitter-description" + id="twitter-description" + name="post-setting-twitter-description" + placeholder=(truncate twitterDescription 155) + focusOut=(action "setTwitterDescription" twitterDescriptionScratch) + stopEnterKeyDownPropagation="true" + update=(action (mut twitterDescriptionScratch)) + data-test-field="twitter-description"}} + {{gh-error-message errors=model.errors property="twitterDescription" data-test-error="twitter-description"}} + {{/gh-form-group}} + +
+ +
+ {{#if twitterImage}} +
+ {{/if}} +
+
{{twitterTitle}}
+
{{truncate twitterDescription 155}}
+ +
+
+
+ +
+
+ {{/if}} + + {{#if (eq subview "facebook-data")}} +
+ +

Facebook Card

+
+
+ +
+
+ {{gh-image-uploader-with-preview + image=model.ogImage + text="Add Facebook image" + update=(action "setOgImage") + remove=(action "clearOgImage") + }} + {{#gh-form-group errors=model.errors hasValidated=model.hasValidated property="ogTitle"}} + + {{gh-input ogTitleScratch + class="post-setting-og-title" + id="og-title" + name="post-setting-og-title" + placeholder=(truncate facebookTitle 40) + focusOut=(action "setOgTitle" ogTitleScratch) + stopEnterKeyDownPropagation="true" + update=(action (mut ogTitleScratch)) + data-test-field="og-title"}} + {{gh-error-message errors=model.errors property="ogTitle" data-test-error="og-title"}} + {{/gh-form-group}} + + {{#gh-form-group errors=model.errors hasValidated=model.hasValidated property="ogDescription"}} + + {{gh-textarea ogDescriptionScratch + class="post-setting-og-description" + id="og-description" + name="post-setting-og-description" + placeholder=(truncate facebookDescription 160) + focusOut=(action "setOgDescription" ogDescriptionScratch) + stopEnterKeyDownPropagation="true" + update=(action (mut ogDescriptionScratch)) + data-test-field="og-description"}} + {{gh-error-message errors=model.errors property="ogDescription" data-test-error="og-description"}} + {{/gh-form-group}} + +
+ +
+ {{#if facebookImage}} +
+ {{/if}} +
+
{{truncate facebookTitle 88}}
+
{{truncate facebookDescription 300}}
+ +
+
+
+ +
+
+ {{/if}} + {{#if (eq subview "codeinjection")}}
diff --git a/ghost/admin/app/validators/post.js b/ghost/admin/app/validators/post.js index 0ad679ed13..e2211c2717 100644 --- a/ghost/admin/app/validators/post.js +++ b/ghost/admin/app/validators/post.js @@ -3,7 +3,20 @@ import moment from 'moment'; import {isEmpty, isPresent} from 'ember-utils'; export default BaseValidator.create({ - properties: ['title', 'customExcerpt', 'codeinjectionHead', 'codeinjectionFoot', 'metaTitle', 'metaDescription', 'publishedAtBlogTime', 'publishedAtBlogDate'], + properties: [ + 'title', + 'customExcerpt', + 'codeinjectionHead', + 'codeinjectionFoot', + 'metaTitle', + 'metaDescription', + 'ogtitle', + 'ogDescription', + 'twitterTitle', + 'twitterDescription', + 'publishedAtBlogTime', + 'publishedAtBlogDate' + ], title(model) { let title = model.get('title'); @@ -64,6 +77,41 @@ export default BaseValidator.create({ } }, + ogTitle(model) { + let ogTitle = model.get('ogTitle'); + + if (!validator.isLength(ogTitle, 0, 300)) { + model.get('errors').add('ogTitle', 'Facebook Title cannot be longer than 300 characters.'); + this.invalidate(); + } + }, + + ogDescription(model) { + let ogDescription = model.get('ogDescription'); + + if (!validator.isLength(ogDescription, 0, 500)) { + model.get('errors').add('ogDescription', 'Facebook Description cannot be longer than 500 characters.'); + this.invalidate(); + } + }, + + twitterTitle(model) { + let twitterTitle = model.get('twitterTitle'); + + if (!validator.isLength(twitterTitle, 0, 300)) { + model.get('errors').add('twitterTitle', 'Twitter Title cannot be longer than 300 characters.'); + this.invalidate(); + } + }, + + twitterDescription(model) { + let twitterDescription = model.get('twitterDescription'); + + if (!validator.isLength(twitterDescription, 0, 500)) { + model.get('errors').add('twitterDescription', 'Twitter Description cannot be longer than 500 characters.'); + this.invalidate(); + } + }, // for posts which haven't been published before and where the blog date/time // is blank we should ignore the validation _shouldValidatePublishedAtBlog(model) { diff --git a/ghost/admin/package.json b/ghost/admin/package.json index dc1f4785f9..c06fe2397b 100644 --- a/ghost/admin/package.json +++ b/ghost/admin/package.json @@ -62,6 +62,7 @@ "ember-cli-pretender": "1.0.1", "ember-cli-selectize": "0.5.12", "ember-cli-shims": "1.1.0", + "ember-cli-string-helpers": "1.4.0", "ember-cli-test-loader": "2.1.0", "ember-cli-uglify": "1.2.0", "ember-composable-helpers": "2.0.1", diff --git a/ghost/admin/tests/acceptance/editor-test.js b/ghost/admin/tests/acceptance/editor-test.js index f679f430f0..53e5d44adc 100644 --- a/ghost/admin/tests/acceptance/editor-test.js +++ b/ghost/admin/tests/acceptance/editor-test.js @@ -641,6 +641,128 @@ describe('Acceptance: Editor', function() { find(testSelector('field', 'codeinjection-head')).length, 'header injection not present after closing subview' ).to.equal(0); + + // ------- + + // open twitter data subview + await click(testSelector('button', 'twitter-data')); + + // twitter title has validation + await fillIn(testSelector('field', 'twitter-title'), Array(302).join('a')); + await triggerEvent(testSelector('field', 'twitter-title'), 'blur'); + + expect( + find(testSelector('error', 'twitter-title')).text().trim(), + 'twitter title too long error' + ).to.match(/cannot be longer than 300/); + + expect( + server.db.posts[0].twitter_title, + 'saved twitter title after validation error' + ).to.be.blank; + + // changing twitter title auto-saves + // twitter title has validation + await fillIn(testSelector('field', 'twitter-title'), 'Test Twitter Title'); + await triggerEvent(testSelector('field', 'twitter-title'), 'blur'); + + expect( + server.db.posts[0].twitter_title, + 'saved twitter title' + ).to.equal('Test Twitter Title'); + + // twitter description has validation + await fillIn(testSelector('field', 'twitter-description'), Array(505).join('a')); + await triggerEvent(testSelector('field', 'twitter-description'), 'blur'); + + expect( + find(testSelector('error', 'twitter-description')).text().trim(), + 'twitter description too long error' + ).to.match(/cannot be longer than 500/); + + expect( + server.db.posts[0].twitter_description, + 'saved twitter description after validation error' + ).to.be.blank; + + // changing twitter description auto-saves + // twitter description has validation + await fillIn(testSelector('field', 'twitter-description'), 'Test Twitter Description'); + await triggerEvent(testSelector('field', 'twitter-description'), 'blur'); + + expect( + server.db.posts[0].twitter_description, + 'saved twitter description' + ).to.equal('Test Twitter Description'); + + // closing subview switches back to main PSM view + await click(testSelector('button', 'close-psm-subview')); + + expect( + find(testSelector('field', 'twitter-title')).length, + 'twitter title not present after closing subview' + ).to.equal(0); + + // ------- + + // open facebook data subview + await click(testSelector('button', 'facebook-data')); + + // facebook title has validation + await fillIn(testSelector('field', 'og-title'), Array(302).join('a')); + await triggerEvent(testSelector('field', 'og-title'), 'blur'); + + expect( + find(testSelector('error', 'og-title')).text().trim(), + 'facebook title too long error' + ).to.match(/cannot be longer than 300/); + + expect( + server.db.posts[0].og_title, + 'saved facebook title after validation error' + ).to.be.blank; + + // changing facebook title auto-saves + // facebook title has validation + await fillIn(testSelector('field', 'og-title'), 'Test Facebook Title'); + await triggerEvent(testSelector('field', 'og-title'), 'blur'); + + expect( + server.db.posts[0].og_title, + 'saved facebook title' + ).to.equal('Test Facebook Title'); + + // facebook description has validation + await fillIn(testSelector('field', 'og-description'), Array(505).join('a')); + await triggerEvent(testSelector('field', 'og-description'), 'blur'); + + expect( + find(testSelector('error', 'og-description')).text().trim(), + 'facebook description too long error' + ).to.match(/cannot be longer than 500/); + + expect( + server.db.posts[0].og_description, + 'saved facebook description after validation error' + ).to.be.blank; + + // changing facebook description auto-saves + // facebook description has validation + await fillIn(testSelector('field', 'og-description'), 'Test Facebook Description'); + await triggerEvent(testSelector('field', 'og-description'), 'blur'); + + expect( + server.db.posts[0].og_description, + 'saved facebook description' + ).to.equal('Test Facebook Description'); + + // closing subview switches back to main PSM view + await click(testSelector('button', 'close-psm-subview')); + + expect( + find(testSelector('field', 'og-title')).length, + 'facebook title not present after closing subview' + ).to.equal(0); }); }); }); diff --git a/ghost/admin/yarn.lock b/ghost/admin/yarn.lock index 43a2426163..74f012a379 100644 --- a/ghost/admin/yarn.lock +++ b/ghost/admin/yarn.lock @@ -628,12 +628,6 @@ babel-plugin-debug-macros@^0.1.10, babel-plugin-debug-macros@^0.1.11: dependencies: semver "^5.3.0" -babel-plugin-debug-macros@^0.1.6: - version "0.1.7" - resolved "https://registry.yarnpkg.com/babel-plugin-debug-macros/-/babel-plugin-debug-macros-0.1.7.tgz#69f5a3dc7d72f781354f18c611a3b007bb223511" - dependencies: - semver "^5.3.0" - babel-plugin-ember-modules-api-polyfill@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/babel-plugin-ember-modules-api-polyfill/-/babel-plugin-ember-modules-api-polyfill-1.4.1.tgz#2b05e1b37e492449319a9cc2bdca460cda70b5b1" @@ -925,40 +919,6 @@ babel-polyfill@6.23.0, babel-polyfill@^6.16.0: core-js "^2.4.0" regenerator-runtime "^0.10.0" -babel-preset-env@^1.2.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/babel-preset-env/-/babel-preset-env-1.4.0.tgz#c8e02a3bcc7792f23cded68e0355b9d4c28f0f7a" - dependencies: - babel-plugin-check-es2015-constants "^6.22.0" - babel-plugin-syntax-trailing-function-commas "^6.22.0" - babel-plugin-transform-async-to-generator "^6.22.0" - babel-plugin-transform-es2015-arrow-functions "^6.22.0" - babel-plugin-transform-es2015-block-scoped-functions "^6.22.0" - babel-plugin-transform-es2015-block-scoping "^6.23.0" - babel-plugin-transform-es2015-classes "^6.23.0" - babel-plugin-transform-es2015-computed-properties "^6.22.0" - babel-plugin-transform-es2015-destructuring "^6.23.0" - babel-plugin-transform-es2015-duplicate-keys "^6.22.0" - babel-plugin-transform-es2015-for-of "^6.23.0" - babel-plugin-transform-es2015-function-name "^6.22.0" - babel-plugin-transform-es2015-literals "^6.22.0" - babel-plugin-transform-es2015-modules-amd "^6.22.0" - babel-plugin-transform-es2015-modules-commonjs "^6.23.0" - babel-plugin-transform-es2015-modules-systemjs "^6.23.0" - babel-plugin-transform-es2015-modules-umd "^6.23.0" - babel-plugin-transform-es2015-object-super "^6.22.0" - babel-plugin-transform-es2015-parameters "^6.23.0" - babel-plugin-transform-es2015-shorthand-properties "^6.22.0" - babel-plugin-transform-es2015-spread "^6.22.0" - babel-plugin-transform-es2015-sticky-regex "^6.22.0" - babel-plugin-transform-es2015-template-literals "^6.22.0" - babel-plugin-transform-es2015-typeof-symbol "^6.23.0" - babel-plugin-transform-es2015-unicode-regex "^6.22.0" - babel-plugin-transform-exponentiation-operator "^6.22.0" - babel-plugin-transform-regenerator "^6.22.0" - browserslist "^1.4.0" - invariant "^2.2.2" - babel-preset-env@^1.5.1: version "1.6.0" resolved "https://registry.yarnpkg.com/babel-preset-env/-/babel-preset-env-1.6.0.tgz#2de1c782a780a0a5d605d199c957596da43c44e4" @@ -1336,19 +1296,7 @@ broccoli-config-replace@^1.1.2: debug "^2.2.0" fs-extra "^0.24.0" -broccoli-debug@^0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/broccoli-debug/-/broccoli-debug-0.6.1.tgz#aec612ba8e5419952f44dc78be52bfabcbc087f6" - dependencies: - broccoli-plugin "^1.2.1" - fs-tree-diff "^0.5.2" - heimdalljs "^0.2.1" - heimdalljs-logger "^0.1.7" - minimatch "^3.0.3" - sanitize-filename "^1.6.1" - tree-sync "^1.2.2" - -broccoli-debug@^0.6.2: +broccoli-debug@^0.6.1, broccoli-debug@^0.6.2: version "0.6.2" resolved "https://registry.yarnpkg.com/broccoli-debug/-/broccoli-debug-0.6.2.tgz#4c6e89459fc3de7d5d4fc7b77e57f46019f44db1" dependencies: @@ -1819,7 +1767,7 @@ browserify@^13.0.0: vm-browserify "~0.0.1" xtend "^4.0.0" -browserslist@^1.3.6, browserslist@^1.4.0, browserslist@^1.5.2, browserslist@^1.7.6: +browserslist@^1.3.6, browserslist@^1.5.2, browserslist@^1.7.6: version "1.7.7" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-1.7.7.tgz#0bd76704258be829b2398bb50e4b62d1a166b0b9" dependencies: @@ -2839,7 +2787,7 @@ ember-cli-app-version@3.0.0: ember-cli-htmlbars "^1.0.0" git-repo-version "0.4.1" -ember-cli-babel@6.6.0, ember-cli-babel@^6.0.0-beta.10, ember-cli-babel@^6.3.0, ember-cli-babel@^6.4.1, ember-cli-babel@^6.6.0: +ember-cli-babel@6.6.0, ember-cli-babel@^6.0.0, ember-cli-babel@^6.0.0-beta.10, ember-cli-babel@^6.0.0-beta.4, ember-cli-babel@^6.0.0-beta.7, ember-cli-babel@^6.0.0-beta.9, ember-cli-babel@^6.1.0, ember-cli-babel@^6.3.0, ember-cli-babel@^6.4.1, ember-cli-babel@^6.6.0: version "6.6.0" resolved "https://registry.yarnpkg.com/ember-cli-babel/-/ember-cli-babel-6.6.0.tgz#a8362bc44841bfdf89b389f3197f104d7ba526da" dependencies: @@ -2866,21 +2814,6 @@ ember-cli-babel@^5.0.0, ember-cli-babel@^5.1.3, ember-cli-babel@^5.1.5, ember-cl ember-cli-version-checker "^1.0.2" resolve "^1.1.2" -ember-cli-babel@^6.0.0, ember-cli-babel@^6.0.0-beta.4, ember-cli-babel@^6.0.0-beta.7, ember-cli-babel@^6.0.0-beta.9, ember-cli-babel@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/ember-cli-babel/-/ember-cli-babel-6.1.0.tgz#d9c83a7d0c67cc8a3ccb9bd082971c3593e54fad" - dependencies: - amd-name-resolver "0.0.6" - babel-plugin-debug-macros "^0.1.6" - babel-plugin-transform-es2015-modules-amd "^6.24.0" - babel-polyfill "^6.16.0" - babel-preset-env "^1.2.0" - broccoli-babel-transpiler "^6.0.0" - broccoli-funnel "^1.0.0" - broccoli-source "^1.1.0" - clone "^2.0.0" - ember-cli-version-checker "^1.2.0" - ember-cli-broccoli-sane-watcher@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/ember-cli-broccoli-sane-watcher/-/ember-cli-broccoli-sane-watcher-2.0.4.tgz#f43f42f75b7509c212fb926cd9aea86ae19264c6" @@ -3158,6 +3091,13 @@ ember-cli-string-helpers@1.0.0: broccoli-funnel "^1.0.1" ember-cli-babel "^5.1.7" +ember-cli-string-helpers@1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/ember-cli-string-helpers/-/ember-cli-string-helpers-1.4.0.tgz#f7142a9499a149f69a9f2662b94f5f21eae0ec48" + dependencies: + broccoli-funnel "^1.0.1" + ember-cli-babel "^5.1.7" + ember-cli-string-utils@^1.0.0, ember-cli-string-utils@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/ember-cli-string-utils/-/ember-cli-string-utils-1.1.0.tgz#39b677fc2805f55173735376fcef278eaa4452a1" @@ -7313,14 +7253,10 @@ route-recognizer@^0.2.3: version "0.2.10" resolved "https://registry.yarnpkg.com/route-recognizer/-/route-recognizer-0.2.10.tgz#024b2283c2e68d13a7c7f5173a5924645e8902df" -rsvp@^3.0.14, rsvp@^3.0.16, rsvp@^3.0.17, rsvp@^3.0.18, rsvp@^3.0.21, rsvp@^3.0.6, rsvp@^3.2.1, rsvp@^3.3.3: +rsvp@^3.0.14, rsvp@^3.0.16, rsvp@^3.0.17, rsvp@^3.0.18, rsvp@^3.0.21, rsvp@^3.0.6, rsvp@^3.2.1, rsvp@^3.3.3, rsvp@^3.5.0: version "3.5.0" resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-3.5.0.tgz#a62c573a4ae4e1dfd0697ebc6242e79c681eaa34" -rsvp@^3.5.0: - version "3.6.2" - resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-3.6.2.tgz#2e96491599a96cde1b515d5674a8f7a91452926a" - rsvp@~3.0.6: version "3.0.21" resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-3.0.21.tgz#49c588fe18ef293bcd0ab9f4e6756e6ac433359f" @@ -7373,12 +7309,6 @@ sane@^1.1.1, sane@^1.6.0: walker "~1.0.5" watch "~0.10.0" -sanitize-filename@^1.6.1: - version "1.6.1" - resolved "https://registry.yarnpkg.com/sanitize-filename/-/sanitize-filename-1.6.1.tgz#612da1c96473fa02dccda92dcd5b4ab164a6772a" - dependencies: - truncate-utf8-bytes "^1.0.0" - sax@~1.2.1: version "1.2.2" resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.2.tgz#fd8631a23bc7826bef5d871bdb87378c95647828" @@ -8094,12 +8024,6 @@ trim-right@^1.0.0, trim-right@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003" -truncate-utf8-bytes@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz#405923909592d56f78a5818434b0b78489ca5f2b" - dependencies: - utf8-byte-length "^1.0.1" - try-resolve@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/try-resolve/-/try-resolve-1.0.1.tgz#cfde6fabd72d63e5797cfaab873abbe8e700e912" @@ -8257,10 +8181,6 @@ user-home@^2.0.0: dependencies: os-homedir "^1.0.0" -utf8-byte-length@^1.0.1: - version "1.0.4" - resolved "https://registry.yarnpkg.com/utf8-byte-length/-/utf8-byte-length-1.0.4.tgz#f45f150c4c66eee968186505ab93fcbb8ad6bf61" - util-deprecate@^1.0.2, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"