diff --git a/ghost/admin/app/components/gh-tag-settings-form.hbs b/ghost/admin/app/components/gh-tag-settings-form.hbs index f4e878ac22..50e4c9ec0c 100644 --- a/ghost/admin/app/components/gh-tag-settings-form.hbs +++ b/ghost/admin/app/components/gh-tag-settings-form.hbs @@ -1,21 +1,42 @@

Basic settings

- - - -

- Start with # to create internal tags - Learn more -

- -
+
+ + + + + + + +

+ Start with # to create internal tags + Learn more +

+
+ + + +
+ +
+
+
+
@@ -45,58 +66,280 @@
- - + + + +

Meta data

-
-
- - - - -

Recommended: 70 characters. You’ve used {{gh-count-down-characters this.scratchTag.metaTitle 70}}

-
- - - - - -

Recommended: 156 characters. You’ve used {{gh-count-down-characters this.scratchTag.metaDescription 156}}

-
-
-
-
- -
-
{{this.seoTitle}}
- -
{{this.seoDescription}}
+
+
+
+
+

Metadata

+

Extra content for search engines.

+
+
+
-
+ {{#liquid-if this.metadataOpen}} +
+
+ + + + +

Recommended: 70 characters. You’ve used {{gh-count-down-characters this.scratchTag.metaTitle 70}}

+
+ + + + + +

Recommended: 156 characters. You’ve used {{gh-count-down-characters this.scratchTag.metaDescription 156}}

+
+ + + + + +
+
+
+ +
+
{{this.seoTitle}}
2.1 + +
{{this.seoDescription}}
+
+
+
+
+ {{/liquid-if}} + +
+
+
+

Twitter Card

+

Customised structured data for Twitter.

+
+
+ +
+
+ {{#liquid-if this.twitterMetadataOpen}} +
+
+ + + + + + + + +

Recommended: 70 characters. You’ve used {{gh-count-down-characters this.scratchTag.twitterTitle 70}}

+
+ + + + + +

Recommended: 156 characters. You’ve used {{gh-count-down-characters this.scratchTag.twitterDescription 156}}

+
+
+
+
+ +
+ {{#if this.twitterImage}} +
+ {{/if}} +
+
{{this.twitterTitle}}
+
{{truncate this.twitterDescription 155}}
+ +
+
+
+
+
+ {{/liquid-if}} +
+
+
+
+

Facebook card

+

Customise Open Graph data.

+
+
+ +
+
+ {{#liquid-if this.facebookMetadataOpen}} +
+
+ + + + + + + + +

Recommended: 70 characters. You’ve used {{gh-count-down-characters this.scratchTag.ogTitle 70}}

+
+ + + + + +

Recommended: 156 characters. You’ve used {{gh-count-down-characters this.scratchTag.ogDescription 156}}

+
+
+
+
+ +
+ {{#if this.facebookImage}} +
+ {{/if}} +
+
{{truncate this.facebookTitle 88}}
+
{{truncate this.facebookDescription 300}}
+ +
+
+
+
+
+ {{/liquid-if}} +
+
+
+
+

Code injection

+

Add styles/scripts to the header and footer.

+
+
+ +
+
+ {{#liquid-if this.codeInjectionOpen}} +
+ + + + + + + + + + + +
+ {{/liquid-if}} +
\ No newline at end of file diff --git a/ghost/admin/app/components/gh-tag-settings-form.js b/ghost/admin/app/components/gh-tag-settings-form.js index 97b17141dc..d421409105 100644 --- a/ghost/admin/app/components/gh-tag-settings-form.js +++ b/ghost/admin/app/components/gh-tag-settings-form.js @@ -2,6 +2,8 @@ import Component from '@ember/component'; import Ember from 'ember'; import {computed} from '@ember/object'; import {htmlSafe} from '@ember/string'; +import {or} from '@ember/object/computed'; +import {run} from '@ember/runloop'; import {inject as service} from '@ember/service'; const {Handlebars} = Ember; @@ -16,6 +18,27 @@ export default Component.extend({ // Allowed actions setProperty: () => {}, + twitterTitle: or('scratchTag.twitterTitle', 'seoTitle'), + twitterDescription: or('scratchTag.twitterDescription', 'seoDescription'), + twitterImage: or('tag.twitterImage', 'tag.featureImage'), + + facebookTitle: or('scratchTag.ogTitle', 'seoTitle'), + facebookDescription: or('scratchTag.ogDescription', 'seoDescription'), + facebookImage: or('tag.ogImage', 'tag.featureImage'), + + accentColor: computed('tag.accentColor', function () { + let color = this.get('tag.accentColor'); + if (color && color[0] === '#') { + return color.slice(1); + } + return color; + }), + + accentColorBackgroundStyle: computed('tag.accentColor', function () { + let color = this.get('tag.accentColor') || '#ffffff'; + return htmlSafe(`background-color: ${color}`); + }), + title: computed('tag.isNew', function () { if (this.get('tag.isNew')) { return 'New tag'; @@ -24,10 +47,8 @@ export default Component.extend({ } }), - seoTitle: computed('scratchTag.{title,metaTitle}', function () { - let metaTitle = this.scratchTag.metaTitle || ''; - - metaTitle = metaTitle.length > 0 ? metaTitle : this.scratchTag.title; + seoTitle: computed('scratchTag.{name,metaTitle}', function () { + let metaTitle = this.scratchTag.metaTitle || this.scratchTag.name; if (metaTitle && metaTitle.length > 70) { metaTitle = metaTitle.substring(0, 70).trim(); @@ -38,14 +59,15 @@ export default Component.extend({ return metaTitle; }), - seoURL: computed('scratchTag.slug', function () { + seoURL: computed('scratchTag.{canonicalUrl,slug}', function () { let blogUrl = this.get('config.blogUrl'); let seoSlug = this.scratchTag.slug || ''; - let seoURL = `${blogUrl}/tag/${seoSlug}`; + let seoURL = this.scratchTag.canonicalUrl || `${blogUrl}/tag/${seoSlug}`; // only append a slash to the URL if the slug exists - if (seoSlug) { + + if (!seoURL.endsWith('/')) { seoURL += '/'; } @@ -77,12 +99,91 @@ export default Component.extend({ this.setProperty(property, value); }, + setTwitterImage(image) { + this.setProperty('twitterImage', image); + }, + + clearTwitterImage() { + this.setProperty('twitterImage', ''); + }, + + setOgImage(image) { + this.setProperty('ogImage', image); + }, + + clearOgImage() { + this.setProperty('ogImage', ''); + }, + setCoverImage(image) { this.setProperty('featureImage', image); }, clearCoverImage() { this.setProperty('featureImage', ''); + }, + + validateCanonicalUrl() { + let newUrl = this.get('scratchTag.canonicalUrl'); + let oldUrl = this.get('tag.canonicalUrl'); + let errMessage = ''; + + this.get('tag.errors').remove('canonicalUrl'); + this.get('tag.hasValidated').removeObject('canonicalUrl'); + + if (newUrl === '') { + this.setProperty('canonicalUrl', ''); + return; + } + + if (!newUrl) { + newUrl = oldUrl; + } + + try { + new URL(newUrl); + this.setProperty('canonicalUrl', ''); + run.schedule('afterRender', this, function () { + this.setProperty('canonicalUrl', newUrl); + }); + } catch (err) { + errMessage = 'The url should be a valid url'; + this.get('tag.errors').add('canonicalUrl', errMessage); + this.get('tag.hasValidated').pushObject('canonicalUrl'); + } + }, + + validateAccentColor() { + let newColor = this.get('accentColor'); + let oldColor = this.get('tag.accentColor'); + let errMessage = ''; + + this.get('tag.errors').remove('accentColor'); + this.get('tag.hasValidated').removeObject('accentColor'); + + if (newColor === '') { + this.setProperty('accentColor', ''); + return; + } + + if (!newColor) { + newColor = oldColor; + } + + if (newColor[0] !== '#') { + newColor = `#${newColor}`; + } + + if (newColor.match(/#[0-9A-Fa-f]{6}$/)) { + this.setProperty('accentColor', ''); + run.schedule('afterRender', this, function () { + this.setProperty('accentColor', newColor); + }); + } else { + errMessage = 'The color should be in valid hex format'; + this.get('tag.errors').add('accentColor', errMessage); + this.get('tag.hasValidated').pushObject('accentColor'); + } } } }); diff --git a/ghost/admin/app/controllers/tag.js b/ghost/admin/app/controllers/tag.js index 7e9c130fd6..2b93beb4ca 100644 --- a/ghost/admin/app/controllers/tag.js +++ b/ghost/admin/app/controllers/tag.js @@ -88,6 +88,9 @@ export default Controller.extend({ tag.setProperties(scratchProps); try { + if (tag.get('errors').length !== 0) { + return; + } yield tag.save(); // replace 'new' route with 'tag' route diff --git a/ghost/admin/app/models/tag.js b/ghost/admin/app/models/tag.js index 03e8eaad06..2c4d164d64 100644 --- a/ghost/admin/app/models/tag.js +++ b/ghost/admin/app/models/tag.js @@ -12,6 +12,16 @@ export default Model.extend(ValidationEngine, { parent: attr('string'), // unused metaTitle: attr('string'), metaDescription: attr('string'), + twitterImage: attr('string'), + twitterTitle: attr('string'), + twitterDescription: attr('string'), + ogImage: attr('string'), + ogTitle: attr('string'), + ogDescription: attr('string'), + codeinjectionHead: attr('string'), + codeinjectionFoot: attr('string'), + canonicalUrl: attr('string'), + accentColor: attr('string'), featureImage: attr('string'), visibility: attr('string', {defaultValue: 'public'}), createdAtUTC: attr('moment-utc'), diff --git a/ghost/admin/app/styles/layouts/settings.css b/ghost/admin/app/styles/layouts/settings.css index da4a296479..c5db1de403 100644 --- a/ghost/admin/app/styles/layouts/settings.css +++ b/ghost/admin/app/styles/layouts/settings.css @@ -813,8 +813,8 @@ p.theme-validation-details { .input-color:after { content: "#"; position: absolute; - top: 6px; - left: 40px; + top: 9px; + left: 43px; color: var(--midlightgrey); font-family: "Consolas", monaco, monospace; font-size: 13px; @@ -825,9 +825,9 @@ p.theme-validation-details { } .input-color input { - padding-left: 48px; - width: 110px; - height: 33px; + padding-left: 52px; + width: 112px; + height: 38px; padding-right: 8px; font-family: "Consolas", monaco, monospace; font-size: 13px; @@ -837,8 +837,8 @@ p.theme-validation-details { position: absolute; top: 1px; left: 1px; - width: 31px; - height: 31px; + width: 36px; + height: 36px; display: inline-block; background-color: var(--lightgrey); border-top-left-radius: 4px; @@ -850,8 +850,8 @@ p.theme-validation-details { .input-color input:focus + .color-box { top: 2px; left: 2px; - width: 30px; - height: 29px; + width: 35px; + height: 34px; border-top-left-radius: 3px; border-bottom-left-radius: 3px; } \ No newline at end of file diff --git a/ghost/admin/app/styles/layouts/tags.css b/ghost/admin/app/styles/layouts/tags.css index f6299a0e64..c285fe8870 100644 --- a/ghost/admin/app/styles/layouts/tags.css +++ b/ghost/admin/app/styles/layouts/tags.css @@ -138,4 +138,39 @@ textarea.gh-tag-details-textarea { margin: 4px 0 0; border: 1px solid var(--lightgrey); min-height: 147px; +} + +.gh-tag-setting-codeinjection .CodeMirror { + padding: 0 !important; + min-height: 240px; +} + +.gh-tag-setting-codeinjection .CodeMirror-scroll { + min-height: 240px; +} + +label.gh-tag-setting-codeheader { + font-size: 1.3rem; + display: flex; + align-items: center; +} + +.gh-tag-settings-multiprop { + display: flex; + max-width: 620px; + width: 100%; +} + +.gh-tag-settings-colorcontainer { + flex-basis: 112px; +} + +@media (max-width: 1080px) { + .gh-tag-settings-multiprop { + flex-direction: column; + } + + .gh-tag-settings-colorcontainer { + flex-basis: unset; + } } \ No newline at end of file