Commit feb18a4f authored by Kingdutch's avatar Kingdutch Committed by Kingdutch

Issue #2938278 by Kingdutch: Allow editing of page title and meta description

parent f851039f
field.widget.settings.yoast_seo_widget:
type: mapping
label: 'Real-Time SEO Analysis widget settings'
mapping:
edit-title:
type: boolean
label: 'Title Editing'
edit-description:
type: boolean
label: 'Description Editing'
/**
* Contains styling for the configuration form.
*/
.nested {
margin-left: 20px;
}
......@@ -77,6 +77,30 @@
padding: 15px 0;
}
.snippet-editor__preview .title.editable,
.snippet-editor__preview .desc.editable {
cursor: text;
}
.snippet-editor__preview .title.editable:focus,
.snippet-editor__preview .desc.editable:focus {
display: block;
box-sizing: border-box;
padding: .3em .4em .3em .5em;
max-width: 100%;
border: 1px solid #b8b8b8;
border-top-color: #999;
background: #fff;
color: #333;
border-radius: 2px;
background: #fcfcfa;
box-shadow: inset 0 1px 2px rgba(0, 0, 0, .125);
font-size: 1em;
color: #595959;
-webkit-transition: border linear 0.2s, box-shadow linear 0.2s;
transition: border linear 0.2s, box-shadow linear 0.2s;
}
.snippet-editor__button {
background: #f7f7f7;
border: 1px solid #dbdbdb;
......@@ -136,7 +160,8 @@
background-size: 25px;
top: 20px;
}
*//
*/
.snippet-editor__input {
display: block;
width: 100%;
......
......@@ -130,6 +130,9 @@
// Set up our event listener for normal form elements
this.$form.change(this.handleChange.bind(this));
// Set up event handlers for editable properties.
this.$form.find('#yoast-snippet').once('yoast_seo-editable').focusout(this.handleBlur.bind(this));
// Set up our event listener for CKEditor instances if any.
// We do this in a setTimeout to allow CKEDITOR to load.
setTimeout(function () {
......@@ -175,6 +178,16 @@
* Handles the blur of a CKEditor field. We just rerun the analysis.
*/
Orchestrator.prototype.handleBlur = function (event) {
var $target = $(event.target);
// If one of the editable properties was edited, update the field values.
if ($target.is('.title.editable')) {
jQuery('[data-drupal-selector="' + this.config.fields.edit_title + '"').val($target.text());
}
else if ($target.is('.desc.editable')) {
jQuery('[data-drupal-selector="' + this.config.fields.edit_description + '"').val($target.text());
}
this.scheduleUpdate();
};
......@@ -318,22 +331,39 @@
}
}
var html =
var title_class = 'title';
var title_editable = '';
if (this.config.enable_editing.title) {
title_class += ' editable';
title_editable = 'contenteditable="true"';
}
var desc_class = 'desc desc-default';
var desc_editable = '';
if (this.config.enable_editing.description) {
desc_class += ' editable';
desc_editable = 'contenteditable="true"';
}
document.getElementById(this.config.targets.snippet).innerHTML =
'<section class="snippet-editor__preview">' +
'<div class="snippet_container snippet-editor__container" id="title_container">' +
'<span class="title" id="snippet_title">' + emphasized_title + '</span>' +
'<span class="title" id="snippet_sitename"></span>' +
'<span class="' + title_class + '"' + title_editable + ' id="snippet_title">' +
emphasized_title +
'</span>' +
'</div>' +
'<div class="snippet_container snippet-editor__container" id="url_container">' +
'<cite class="url urlBase" id="snippet_citeBase">' + this.config.base_root + '</cite>' +
'<cite class="url" id="snippet_cite">' + this.data.url + '</cite>' +
'</div>' +
'<div class="snippet_container snippet-editor__container" id="meta_container">' +
'<span class="desc desc-default" id="snippet_meta">' + this.data.meta + '</span>' +
'<span class="' + desc_class + '"' + desc_editable + ' id="snippet_meta">' +
this.data.meta +
'</span>' +
'</div>' +
'</section>';
document.getElementById(this.config.targets.snippet).innerHTML = html;
};
})(jQuery);
......@@ -4,11 +4,17 @@ namespace Drupal\yoast_seo\Entity;
use Drupal\Core\Entity\EntityInterface;
/**
* A class to encapsulate entity analysis results.
*/
class EntityPagePreview implements EntityPreviewInterface {
protected $entity;
protected $language;
/**
* {@inheritdoc}
*/
public function __construct(EntityInterface $entity) {
$this->entity = $entity;
$this->language = $entity->language();
......@@ -27,4 +33,5 @@ class EntityPagePreview implements EntityPreviewInterface {
public function getEntity() {
return $this->entity;
}
}
\ No newline at end of file
}
......@@ -26,4 +26,5 @@ interface EntityPreviewInterface {
* The entity object.
*/
public function getEntity();
}
\ No newline at end of file
}
......@@ -144,10 +144,20 @@ class EntityAnalyser {
$html = $this->renderEntity($entity);
$tags = $this->metatagManager->tagsFromEntityWithDefaults($entity);
$data = $this->metatagManager->generateRawElements($tags, $entity);
$metatags = $this->metatagManager->tagsFromEntityWithDefaults($entity);
// Turn our tag renderable into a key => value array.
// Trigger hook_metatags_alter().
// Allow modules to override tags or the entity used for token replacements.
// Also used to override editable titles and descriptions.
$context = [
'entity' => $entity,
];
\Drupal::service('module_handler')->alter('metatags', $metatags, $context);
// Resolve the metatags from tokens into actual values.
$data = $this->metatagManager->generateRawElements($metatags, $entity);
// Turn our tag render array into a key => value array.
foreach ($data as $name => $tag) {
$data[$name] = $tag['#attributes']['content'];
}
......
......@@ -22,7 +22,7 @@ class YoastSeoFormatter extends FormatterBase {
* {@inheritdoc}
*/
public function viewElements(FieldItemListInterface $items, $langcode) {
$elements = array();
$elements = [];
$yoast_seo_manager = \Drupal::service('yoast_seo.manager');
foreach ($items as $delta => $item) {
$status = $yoast_seo_manager->getScoreStatus($item->status);
......@@ -41,9 +41,9 @@ class YoastSeoFormatter extends FormatterBase {
];
$output = \Drupal::service('renderer')->render($overall_score_tpl);
$elements[$delta] = array(
$elements[$delta] = [
'#markup' => $output,
);
];
}
return $elements;
......
......@@ -43,6 +43,16 @@ class YoastSeoItem extends FieldItemBase {
'length' => 256,
'not null' => FALSE,
],
'title' => [
'type' => 'varchar',
'length' => 1024,
'not null' => FALSE,
],
'description' => [
'type' => 'varchar',
'length' => 1024,
'not null' => FALSE,
],
],
];
}
......@@ -55,6 +65,10 @@ class YoastSeoItem extends FieldItemBase {
->setLabel(t('Status'));
$properties['focus_keyword'] = DataDefinition::create('string')
->setLabel(t('Focus Keyword'));
$properties['title'] = DataDefinition::create('string')
->setLabel(t('Edited title'));
$properties['description'] = DataDefinition::create('string')
->setLabel(t('Edited description'));
return $properties;
}
......
......@@ -131,6 +131,18 @@ class YoastSeoWidget extends WidgetBase implements ContainerFactoryPluginInterfa
$js_config['fields']['focus_keyword'] = $element['yoast_seo']['focus_keyword']['#id'];
$js_config['fields']['seo_status'] = $element['yoast_seo']['status']['#id'];
// Add fields to store editable properties.
foreach (['title', 'description'] as $property) {
if ($this->getSetting('edit_' . $property)) {
$element['yoast_seo']['edit_' . $property] = [
'#id' => Html::getUniqueId('yoast_seo-' . $delta . '-' . $property),
'#type' => 'hidden',
'#default_value' => isset($items[$delta]->{$property}) ? $items[$delta]->{$property} : NULL,
];
$js_config['fields']['edit_' . $property] = $element['yoast_seo']['edit_' . $property]['#id'];
}
}
$form_object = $form_state->getFormObject();
if ($form_object instanceof EntityForm) {
......@@ -164,10 +176,62 @@ class YoastSeoWidget extends WidgetBase implements ContainerFactoryPluginInterfa
foreach ($values as &$value) {
$value['status'] = $value['yoast_seo']['status'];
$value['focus_keyword'] = $value['yoast_seo']['focus_keyword'];
$value['title'] = $value['yoast_seo']['edit_title'];
$value['description'] = $value['yoast_seo']['edit_description'];
}
return $values;
}
/**
* {@inheritdoc}
*/
public static function defaultSettings() {
return [
'edit_title' => FALSE,
'edit_description' => FALSE,
] + parent::defaultSettings();
}
/**
* {@inheritdoc}
*/
public function settingsForm(array $form, FormStateInterface $form_state) {
$form = parent::settingsForm($form, $form_state);
$form['edit_title'] = [
'#type' => 'checkbox',
'#title' => $this->t('Enable title editing'),
'#description' => $this->t('When this is checked the page title will be editable through the Real-Time SEO widget.'),
'#default_value' => $this->getSetting('edit_title'),
];
$form['edit_description'] = [
'#type' => 'checkbox',
'#title' => $this->t('Enable description editing'),
'#description' => $this->t('When this is checked the meta description will be editable through the Real-Time SEO widget.'),
'#default_value' => $this->getSetting('edit_description'),
];
return $form;
}
/**
* {@inheritdoc}
*/
public function settingsSummary() {
$summary = [];
if ($this->getSetting('edit_title')) {
$summary[] = 'Title editing enabled';
}
if ($this->getSetting('edit_description')) {
$summary[] = 'Description editing enabled';
}
return $summary;
}
/**
* Returns the JavaScript configuration for this widget.
*
......@@ -189,8 +253,14 @@ class YoastSeoWidget extends WidgetBase implements ContainerFactoryPluginInterfa
'base_root' => $base_root,
// Set up score to indiciator word rules.
'score_status' => $score_to_status_rules,
// Possibly allow properties to be editable.
'enable_editing' => [],
];
foreach (['title', 'description'] as $property) {
$configuration['enable_editing'][$property] = $this->getSetting('edit_' . $property);
}
// Set up the names of the text outputs.
foreach (self::$jsTargets as $js_target_name => $js_target_id) {
$configuration['targets'][$js_target_name] = $js_target_id;
......
......@@ -6,6 +6,7 @@
*/
use Drupal\views\Views;
use Drupal\Core\Entity\Sql\SqlContentEntityStorage;
/**
* Remove the SEO status from the content overview.
......@@ -22,4 +23,49 @@ function yoast_seo_update_8201() {
$content_view->save();
}
}
}
\ No newline at end of file
}
/**
* Add a title and description property to the yoast_seo field.
*/
function yoast_seo_update_8202() {
$field_type = 'yoast_seo';
$add_properties = [
'title',
'description',
];
$manager = \Drupal::entityDefinitionUpdateManager();
$field_map = \Drupal::service('entity_field.manager')->getFieldMapByFieldType($field_type);
foreach ($field_map as $entity_type_id => $fields) {
foreach (array_keys($fields) as $field_name) {
$field_storage_definition = $manager->getFieldStorageDefinition($field_name, $entity_type_id);
$storage = \Drupal::entityTypeManager()->getStorage($entity_type_id);
if ($storage instanceof SqlContentEntityStorage) {
$table_mapping = $storage->getTableMapping([
$field_name => $field_storage_definition,
]);
$table_names = $table_mapping->getDedicatedTableNames();
$columns = $table_mapping->getColumnNames($field_name);
foreach ($table_names as $table_name) {
$field_schema = $field_storage_definition->getSchema();
$schema = \Drupal::database()->schema();
foreach ($add_properties as $new_property) {
$field_exists = $schema->fieldExists($table_name, $columns[$new_property]);
$table_exists = $schema->tableExists($table_name);
if (!$field_exists && $table_exists) {
$schema->addField($table_name, $columns[$new_property], $field_schema['columns'][$new_property]);
}
}
}
}
$manager->updateFieldStorageDefinition($field_storage_definition);
}
}
}
......@@ -24,6 +24,7 @@ yoast_seo_admin:
version: 1.x
css:
theme:
css/yoast_seo.config.css: {}
css/yoast_seo.message.css: {}
yoast_seo_view:
......@@ -43,4 +44,4 @@ analytics_core:
/libraries/rtseo.js/dist/rtseo.js: {}
css:
theme:
/libraries/rtseo.js/dist/rtseo.min.css: {}
\ No newline at end of file
/libraries/rtseo.js/dist/rtseo.min.css: {}
......@@ -93,3 +93,50 @@ function yoast_seo_entity_type_build(array &$entity_types) {
}
}
}
/**
* Implements hook_metatags_alter().
*
* If an entity has values for the custom title or description fields then we
* use those values in place of the metatags defaults.
*/
function yoast_seo_metatags_alter(array &$metatags, array $context) {
// Without entity there is nothing for us to do.
if (empty($context['entity'])) {
return;
}
/** @var \Drupal\Core\Entity\EntityInterface $entity */
$entity = $context['entity'];
/** @var \Drupal\yoast_seo\SeoManager $seo_manager */
$seo_manager = \Drupal::service('yoast_seo.manager');
$entity_type = $entity->getEntityTypeId();
$bundle = $entity->bundle();
// Abort early if we're not enabled for this entity type.
if (!$seo_manager->isEnabledFor($entity_type, $bundle)) {
return;
}
$field = $entity->get('field_yoast_seo')->first();
// If the field has no value then we're done as well.
if (empty($field)) {
return;
}
$values = $field->getValue();
if ($seo_manager->isEditingEnabledFor($entity_type, $bundle, 'title') &&
!empty($values['title'])) {
$metatags['title'] = $values['title'];
}
if ($seo_manager->isEditingEnabledFor($entity_type, $bundle, 'description') &&
!empty($values['description'])) {
$metatags['description'] = $values['description'];
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment