Skip to content
Snippets Groups Projects
Commit a670f092 authored by Sébastien Theys's avatar Sébastien Theys
Browse files

[IMP] website: improve Optimize SEO and Page Manager

Modal generic:
- Fix footer overflowing buttons by pushing them to a new line.

Page manager:
- Add an SEO column to easily see which pages have incomplete SEO.
- Add an edit SEO column to navigate to the SEO modal on said page.
- Hide "edit in backend" column as debug.
- Generally improve the whole view by adding appropriate titles on icons, etc.

SEO modal:
- Improve general style.
- Add description length alert.
- Restructure the keyword table/suggestions.
- Reset page meta to their initial value on Discard.
- Various small usability improvements.

PR: #26897
task-1850579
parent 7a298f89
No related branches found
No related tags found
No related merge requests found
......@@ -26,6 +26,7 @@
}
.modal-footer {
flex-wrap: wrap;
text-align: left;
justify-content: flex-start;
......@@ -39,6 +40,10 @@
> :not(:first-child) { margin-left: .25rem; }
> :not(:last-child) { margin-right: .25rem; }
}
button {
margin-bottom: .5rem;
}
}
}
......
......@@ -676,11 +676,17 @@ class SeoMetadata(models.AbstractModel):
_name = 'website.seo.metadata'
_description = 'SEO metadata'
is_seo_optimized = fields.Boolean("SEO optimized", compute='_compute_is_seo_optimized')
website_meta_title = fields.Char("Website meta title", translate=True)
website_meta_description = fields.Text("Website meta description", translate=True)
website_meta_keywords = fields.Char("Website meta keywords", translate=True)
website_meta_og_img = fields.Char("Website opengraph image")
@api.multi
def _compute_is_seo_optimized(self):
for record in self:
record.is_seo_optimized = record.website_meta_title and record.website_meta_description and record.website_meta_keywords
def _default_website_meta(self):
""" This method will return default meta information. It return the dict
contains meta property as a key and meta content as a value.
......
This diff is collapsed.
......@@ -193,7 +193,6 @@ body .modal {
#language-box {
padding-right: 25px;
background-color: white;
margin-left: -1px;
}
.o_seo_og_image {
.o_meta_img {
......@@ -259,121 +258,7 @@ body .modal {
}
}
.table {
td {
vertical-align: middle;
&:first-child {
padding-right: 15px;
border-width: 0;
width: 35%;
}
&:last-child {
visibility: hidden;
}
}
> tfoot {
display: none;
}
&.js_seo_has_content {
td {
&:first-child {
width: 55%;
padding-right: 15px;
border: 1px solid $o-we-color-text-light;
text-align: right;
background-color: $o-we-color-paper;
@include media-breakpoint-up(lg) {
width: 38%;
}
}
&:last-child {
border: none;
visibility: visible;
padding-left: 15px;
@include media-breakpoint-up(lg) {
padding-left: 50px;
}
}
}
tbody {
td {
padding: 5px;
transition: padding .3s ease 0s;
&:first-child {
border-width: 0 1px;
.label {
position: relative;
display: inline-block;
padding: 7px 35px;
font-size: 16px;
font-weight: normal;
.oe_remove {
@include o-w-close-icon(10px, white, $o-we-color-danger, 2px); // FIXME
@include o-position-absolute(5px, 5px);
}
}
}
&:last-child {
.label {
display: block;
font-size: 12px;
font-weight: normal;
opacity: 0.8;
cursor: pointer;
&:hover {
opacity: 1;
}
}
}
}
tr {
animation: fadeInDownSmall .3s ease 0s 1 normal none running;
&:first-child {
td:first-child {
padding-top: 10px;
}
}
&:last-child {
td:first-child {
padding-bottom: 10px;
border-bottom-width: 1px;
}
}
}
}
> tfoot {
display: table-footer-group;
hr {
margin: 10px 0;
}
td, td:first-child {
border: none;
background: none;
}
}
}
}
li.oe_seo_preview_g {
div.oe_seo_preview_g {
list-style: none;
font-family: arial, sans-serif;
......@@ -396,9 +281,6 @@ body .modal {
font-size: 14px;
line-height: 18px;
}
.st {
height: 50px;
}
}
}
}
......
......@@ -6,72 +6,10 @@
</t>
</t>
<div t-name="website.seo_configuration">
<section class="row">
<div class="col-lg-6">
<div class="mt16" role="form">
<div class="form-group row">
<label for="seo_page_title" class="col-3 col-form-label">Page Title</label>
<div class="col-9">
<input type="text" name="seo_page_title" class="form-control" maxlength="70" size="70"/>
</div>
</div>
<div class="form-group row">
<label for="seo_page_description" class="col-3 col-form-label">Description</label>
<div class="col-9">
<textarea name="seo_page_description" class="form-control" style="max-width: 100%;height:145px;max-height:145px" t-att-maxlength="widget.maxDescriptionSize"/>
</div>
</div>
</div>
</div>
<div class="col-lg-6">
<h5 class="mt16">Preview <small>how your page will be listed on Google</small></h5>
<div>
<div class="card mb0">
<div class="card-body">
<div class="js_seo_preview"/>
</div>
</div>
</div>
</div>
</section>
<hr/>
<div class="js_seo_tips" />
<section class="js_seo_keywords_list">
<h4 class="mt16">Define Keywords <small>describing your page content</small></h4>
<table class="table table-responsive">
<thead>
<tr>
<td>
<div class="form-inline" role="form">
<div class="input-group">
<input type="text" style="min-width:170px" name="seo_page_keywords" class="form-control" placeholder="Keyword" maxlength="30"/>
<span class="input-group-append">
<select name="seo_page_language" id="language-box" class="btn form-control"/>
</span>
<span class="input-group-append">
<button data-action="add" class="btn btn-success" type="button">Add</button>
</span>
</div>
</div>
</td>
<td>Suggested<br/><small class="text-muted">Most searched topics related to your keywords, ordered by importance:</small></td>
</tr>
</thead>
<tfoot>
<tr>
<td/>
<td><hr/>
<small class="text-muted">Legend:</small>
<span class="badge badge-secondary">Not used</span>
<span class="badge badge-success">In title</span>
<span class="badge badge-primary">In description</span>
<span class="badge badge-info">In page's content</span>
</td>
</tr>
</tfoot>
</table>
</section>
<div t-name="website.seo_configuration" role="form">
<section class="js_seo_meta_title_description"/>
<section class="js_seo_meta_keywords"/>
<section class="js_seo_image"/>
</div>
<t t-name="website.seo_suggestion_list">
......@@ -86,35 +24,27 @@
</tbody>
</t>
<t t-name="website.seo_tip">
<div t-attf-class="alert alert-#{ widget.type }" role="alert">
<t t-raw="widget.message"/>
</div>
</t>
<t t-name="website.seo_keyword">
<tr>
<td>
<span t-attf-title="#{ widget.tooltip() }" t-attf-class="oe_seo_keyword #{ widget.highlight() } js_seo_keyword" t-att-data-keyword="widget.keyword">
<t t-raw="widget.keyword"/> <a href="#" class="oe_remove" data-action="remove-keyword"/>
</span>
</td>
<td class="js_seo_keyword_suggestion">
<!-- filled in JS -->
</td>
<tr class="js_seo_keyword" t-att-data-keyword="widget.keyword">
<td t-esc="widget.keyword"/>
<td class="text-center"><i t-if="widget.used_h1" class="fa fa-check" t-attf-title="{{ widget.keyword }} is used in page first level heading"/></td>
<td class="text-center"><i t-if="widget.used_h2" class="fa fa-check" t-attf-title="{{ widget.keyword }} is used in page second level heading"/></td>
<td class="text-center"><i class="js_seo_keyword_title fa fa-check" style="visibility: hidden;" t-attf-title="{{ widget.keyword }} is used in page title"/></td>
<td class="text-center"><i class="js_seo_keyword_description fa fa-check" style="visibility: hidden;" t-attf-title="{{ widget.keyword }} is used in page description"/></td>
<td class="text-center"><i t-if="widget.used_content" class="fa fa-check" t-attf-title="{{ widget.keyword }} is used in page content"/></td>
<td class="js_seo_keyword_suggestion"/>
<td class="text-center"><a href="#" class="oe_remove" data-action="remove-keyword" t-attf-title="Remove {{ widget.keyword }}"><i class="fa fa-trash"/></a></td>
</tr>
</t>
<t t-name="website.seo_suggestion">
<li class="list-inline-item oe_seo_suggestion">
<span t-attf-title="#{ widget.tooltip() }" t-attf-class="oe_seo_keyword #{ widget.highlight() } js_seo_suggestion" t-att-data-keyword="widget.keyword">
<t t-raw="widget.keyword"/>
</span>
<li class="list-inline-item">
<span class="js_seo_suggestion badge badge-info" t-att-data-keyword="widget.keyword" t-esc="widget.keyword"/>
</li>
</t>
<t t-name="website.seo_preview">
<li class="oe_seo_preview_g">
<div class="oe_seo_preview_g">
<div class="rc">
<div class="r"><t t-esc="widget.title"/></div>
<div class="s">
......@@ -122,7 +52,70 @@
<div class="st"><t t-esc="widget.description"/></div>
</div>
</div>
</li>
</div>
</t>
<div t-name="website.seo_meta_title_description">
<h4><small>Optimize this page's referencing in search engines</small></h4>
<div class="row">
<div class="col-lg-6">
<div class="form-group">
<label for="website_meta_title">
Title <i class="fa fa-question-circle-o" title="The title will take a default value unless you specify one."/>
</label>
<input type="text" name="website_meta_title" id="website_meta_title" class="form-control" maxlength="70" size="70"/>
</div>
<div class="form-group">
<label for="website_meta_description">
Description <i class="fa fa-question-circle-o" title="The description will be generated by search engines based on page content unless you specify one."/>
</label>
<textarea name="website_meta_description" id="website_meta_description" class="form-control" style="height:120px;"/>
<div class="alert alert-warning mt16 mb0 small" id="website_meta_description_warning" style="display: none;"/>
</div>
</div>
<div class="col-lg-6">
<div class="card-header">Preview</div>
<div class="card mb0 p-0">
<div class="card-body">
<div class="js_seo_preview"/>
</div>
</div>
</div>
</div>
</div>
<t t-name="website.seo_meta_keywords">
<label for="website_meta_keywords">
Keywords
</label>
<div class="form-inline" role="form">
<div class="input-group">
<input type="text" name="website_meta_keywords" id="website_meta_keywords" class="form-control" placeholder="Keyword" maxlength="30"/>
<span class="input-group-append">
<select name="seo_page_language" id="language-box" class="btn form-control"/>
</span>
<span class="input-group-append">
<button data-action="add" class="btn btn-success" type="button">Add</button>
</span>
</div>
</div>
<div class="table-responsive mt16">
<table class="table">
<thead>
<tr>
<th>Keyword</th>
<th class="text-center" title="Used in page first level heading">H1</th>
<th class="text-center" title="Used in page second level heading">H2</th>
<th class="text-center" title="Used in page title">T</th>
<th class="text-center" title="Used in page description">D</th>
<th class="text-center" title="Used in page content">C</th>
<th title="Most searched topics related to your keyword, ordered by importance">Related keywords</th>
<th class="text-center"><i class="fa fa-trash" title="Remove"/></th>
</tr>
</thead>
<!-- body inserted in JS -->
</table>
</div>
</t>
<div t-name="website.seo_meta_image_selector" class="o_seo_og_image">
......@@ -130,10 +123,9 @@
</div>
<t t-name="website.og_image_body">
<h4><small>Select an image for social share</small></h4>
<div class="row">
<div class="col-md-6">
<h5 class="mt16 mb4">Image for Social Share</h5>
<div class="text-muted mb32">Select an image to use in social share.</div>
<div class="col-lg-6">
<t t-foreach="widget.images" t-as="image">
<div t-attf-class="o_meta_img mt4 #{image === widget.activeMetaImg and ' o_active_image' or ''}">
<img t-att-src="image"/>
......@@ -147,13 +139,14 @@
<i class="fa fa-upload"/>
</div>
</div>
<div class="col-md-6">
<h6 class="mt16">Preview <small>how your page will be displayed on social media</small></h6>
<div class="card p-0">
<div class="col-lg-6">
<div class="card p-0 mb16">
<div class="card-header">Preview</div>
<img class="card-img-top o_meta_active_img" t-att-src="widget.activeMetaImg"/>
<div class="card-body px-3 py-2">
<h6 class="text-alpha card-title mb0"><t t-esc="widget.metaTitle"/></h6>
<small class="card-subtitle text-muted"><t t-esc="widget.serverUrl"/></small>
<p t-esc="widget.metaDescription"/>
</div>
</div>
</div>
......
......@@ -132,13 +132,13 @@
'data-oe-company-name': res_company.name
}"/>
<t t-if="not title">
<t t-if="not additional_title and main_object and 'name' in main_object">
<t t-set="additional_title" t-value="main_object.name"/>
</t>
<t t-if="main_object and 'website_meta_title' in main_object and main_object.website_meta_title">
<t t-set="title" t-value="main_object.website_meta_title"/>
</t>
<t t-else="">
<t t-if="not additional_title and main_object and 'name' in main_object">
<t t-set="additional_title" t-value="main_object.name"/>
</t>
<t t-set="title"><t t-if="additional_title"><t t-raw="additional_title"/> | </t><t t-raw="(website or res_company).name"/></t>
</t>
</t>
......@@ -1195,7 +1195,8 @@ Sitemap: <t t-esc="url_root"/>sitemap.xml
<th class="text-center"><i title="Is the page included in the main menu?" class="fa fa-thumb-tack"></i></th>
<th class="text-center"><i title="Is the page published?" class="fa fa-eye"></i></th>
<th class="text-center"><i title="Is the page indexed by search engines?" class="fa fa-globe"></i></th>
<th style='width:140px;'></th>
<th class="text-center"><i title="Is the page SEO optimized?" class="fa fa-search"></i></th>
<th></th>
</tr>
</thead>
<t t-set='prev_page' t-value='False' />
......@@ -1219,31 +1220,45 @@ Sitemap: <t t-esc="url_root"/>sitemap.xml
</template>
<template id="one_page_line">
<t t-set='specific_page' t-value="page.website_id" />
<t t-set='final_page' t-value="(next_page and page.url != next_page.url) or not next_page or specific_page"/>
<tr t-att-style='not final_page and "color:#999"'>
<td>
<t t-set='specific_page' t-value="page.website_id"/>
<t t-set='final_page' t-value="(next_page and page.url != next_page.url) or not next_page or specific_page"/>
<tr t-att-style='not final_page and "color:#999"'>
<td>
<t groups="website.group_multi_website">
<t t-if='specific_page and prev_page and prev_page.url == page.url and not prev_page.website_id'><i class="fa fa-level-up fa-rotate-90 ml32 mr4"></i></t>
<i t-else="1" class="fa fa-globe mr4" t-att-style='specific_page and "visibility:hidden;"'/>
<i t-if='specific_page and prev_page and prev_page.url == page.url and not prev_page.website_id' class="fa fa-level-up fa-rotate-90 ml32 mr4"/>
<i t-else="1" class="fa fa-globe mr4" t-att-style="'visibility:hidden;' if specific_page else ''"/>
</t>
<i t-if="page.is_homepage" class="fa fa-home" title="Home"></i> <span t-esc="page.name"/>
</td>
<td><a t-if='final_page' t-attf-href="{{page.url}}"><t t-esc="page.url"/></a></td>
<td class="text-center"><i t-att-class="'fa fa-check' if page.menu_ids else 'fa fa-times text-muted'" t-att-title="'Checked' if page.menu_ids else 'Not checked'"></i></td>
<t t-set='date_formatted'><t t-options='{"widget": "date"}' t-esc="page.date_publish"/></t>
<td class="text-center">
<i t-att-title="not page.is_visible and page.website_published and 'This page will be visible on ' + date_formatted"
t-att-class="'fa fa-check' if page.is_visible and page.website_published else 'fa fa-eye-slash' if not page.is_visible and page.website_published else 'fa fa-times text-muted'"></i>
</td>
<td class="text-center"><i t-att-class="'fa fa-check' if page.website_indexed else 'fa fa-times text-muted'" t-att-title="'Checked' if page.website_indexed else 'Not checked'"></i></td>
<td class="text-right" style="white-space:nowrap;">
<a class="mr4 fa fa-cog js_page_properties" t-att-data-id="page.id" href="#" title="Manage this page"></a>
<a class="mr4 fa fa-pencil-square-o" t-attf-href="/web#id=#{page.view_id.id}&amp;view_type=form&amp;model=ir.ui.view" title="Edit code in backend"></a>
<a class="mr4 fa fa-clone js_clone_page" t-att-data-id="page.id" href="#" title="Clone this page"></a>
<a class="fa fa-trash js_delete_page" t-att-data-id="page.id" href="#" title="Delete this page"></a>
</td>
</tr>
<i t-if="page.is_homepage" class="fa fa-home" title="Home"/> <span t-esc="page.name"/>
</td>
<td>
<a t-if='final_page' t-att-href="page.url"><t t-esc="page.url"/></a>
</td>
<td class="text-center">
<i t-if="page.menu_ids" class="fa fa-check" title="In main menu"/>
<i t-else="" class="fa fa-times text-muted" title="Not in main menu"/>
</td>
<td class="text-center">
<t t-set='date_formatted'><t t-options='{"widget": "date"}' t-esc="page.date_publish"/></t>
<i t-if="page.is_visible" class="fa fa-check" title="Visible"/>
<i t-elif="page.website_published" class="fa fa-eye-slash" t-attf-title="This page will be visible on {{ date_formatted }}"/>
<i t-else="" class="fa fa-times text-muted" title="Not visible"/>
</td>
<td class="text-center">
<i t-if="page.website_indexed" class="fa fa-check" title="Indexed"/>
<i t-else="" class="fa fa-times text-muted" title="Not indexed"/>
</td>
<td class="text-center">
<i t-if="page.is_seo_optimized" class="fa fa-check" title="SEO optimized"/>
<i t-else="" class="fa fa-times text-muted" title="Not SEO optimized"/>
</td>
<td class="text-right" style="white-space:nowrap;">
<a class="mr4 fa fa-cog js_page_properties" href="#" t-att-data-id="page.id" title="Manage this page"/>
<a class="mr4 fa fa-search" t-attf-href="{{ page.url}}?enable_seo" title="Optimize SEO of this page"/>
<a groups="base.group_no_one" class="mr4 fa fa-bug" t-attf-href="/web#id=#{page.view_id.id}&amp;view_type=form&amp;model=ir.ui.view" title="Edit code in backend"/>
<a class="mr4 fa fa-clone js_clone_page" t-att-data-id="page.id" href="#" title="Clone this page"/>
<a class="fa fa-trash js_delete_page" t-att-data-id="page.id" href="#" title="Delete this page"/>
</td>
</tr>
</template>
</odoo>
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment