-
-
-
{{this.seoTitle}}
-
{{this.seoURL}}
-
{{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}}
+
+
+
+
+
+
+
+
+
+ {{/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}}
+
+
+
+
+ {{/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}}
+
+
+
+
+ {{/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