feat: add SEO content scoring panel to article editor
JavaScript-based SEO analysis with 7 checks (OG title, description, image, SEO title, meta description, title length, description length). Shows pass/fail dots and overall score. Closes #68
This commit is contained in:
@@ -240,3 +240,16 @@
|
||||
color: #d32f2f;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* SEO scoring panel */
|
||||
.mokoog-seo-score { margin: 15px 0; padding: 15px; background: #f8f9fa; border-radius: 8px; border: 1px solid #dee2e6; }
|
||||
.mokoog-seo-heading { margin: 0 0 10px; font-size: 14px; color: #666; }
|
||||
.mokoog-seo-list { list-style: none; padding: 0; margin: 0 0 10px; }
|
||||
.mokoog-seo-item { padding: 4px 0; font-size: 13px; display: flex; align-items: center; gap: 8px; }
|
||||
.mokoog-seo-dot { width: 10px; height: 10px; border-radius: 50%; flex-shrink: 0; }
|
||||
.mokoog-seo-pass { background: #2e7d32; }
|
||||
.mokoog-seo-fail { background: #d32f2f; }
|
||||
.mokoog-seo-total { font-size: 14px; font-weight: 600; padding-top: 8px; border-top: 1px solid #dee2e6; }
|
||||
.mokoog-seo-total-good { color: #2e7d32; }
|
||||
.mokoog-seo-total-ok { color: #f57c00; }
|
||||
.mokoog-seo-total-bad { color: #d32f2f; }
|
||||
|
||||
@@ -367,17 +367,90 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
document.getElementById('mokoog-sl-domain').textContent = domain;
|
||||
}
|
||||
|
||||
// SEO scoring panel
|
||||
var seoChecks = [
|
||||
{ id: 'og-title', label: 'OG Title', check: function() { return fields.ogTitle && fields.ogTitle.value.length > 0; }},
|
||||
{ id: 'og-desc', label: 'OG Description', check: function() { return fields.ogDesc && fields.ogDesc.value.length > 0; }},
|
||||
{ id: 'og-image', label: 'OG Image', check: function() { return fields.ogImage && fields.ogImage.value.length > 0; }},
|
||||
{ id: 'seo-title', label: 'SEO Title', check: function() { return fields.seoTitle && fields.seoTitle.value.length > 0; }},
|
||||
{ id: 'meta-desc', label: 'Meta Description', check: function() { return fields.metaDescription && fields.metaDescription.value.length > 0; }},
|
||||
{ id: 'title-length', label: 'Title Length (\u226460)', check: function() {
|
||||
var t = (fields.ogTitle && fields.ogTitle.value) || (fields.articleTitle && fields.articleTitle.value) || '';
|
||||
return t.length > 0 && t.length <= 60;
|
||||
}},
|
||||
{ id: 'desc-length', label: 'Description Length (\u2264160)', check: function() {
|
||||
var d = (fields.ogDesc && fields.ogDesc.value) || (fields.metaDesc && fields.metaDesc.value) || '';
|
||||
return d.length > 0 && d.length <= 160;
|
||||
}}
|
||||
];
|
||||
|
||||
var seoPanel = document.createElement('div');
|
||||
seoPanel.className = 'mokoog-seo-score';
|
||||
|
||||
var seoHeading = document.createElement('h4');
|
||||
seoHeading.className = 'mokoog-seo-heading';
|
||||
seoHeading.textContent = 'SEO Analysis';
|
||||
seoPanel.appendChild(seoHeading);
|
||||
|
||||
var seoList = document.createElement('ul');
|
||||
seoList.className = 'mokoog-seo-list';
|
||||
|
||||
var seoDots = {};
|
||||
seoChecks.forEach(function (chk) {
|
||||
var li = document.createElement('li');
|
||||
li.className = 'mokoog-seo-item';
|
||||
|
||||
var dot = document.createElement('span');
|
||||
dot.className = 'mokoog-seo-dot mokoog-seo-fail';
|
||||
seoDots[chk.id] = dot;
|
||||
li.appendChild(dot);
|
||||
|
||||
var label = document.createElement('span');
|
||||
label.textContent = chk.label;
|
||||
li.appendChild(label);
|
||||
|
||||
seoList.appendChild(li);
|
||||
});
|
||||
|
||||
seoPanel.appendChild(seoList);
|
||||
|
||||
var seoTotal = document.createElement('div');
|
||||
seoTotal.className = 'mokoog-seo-total';
|
||||
seoPanel.appendChild(seoTotal);
|
||||
|
||||
wrapper.parentNode.insertBefore(seoPanel, wrapper.nextSibling);
|
||||
|
||||
function updateSeoScore() {
|
||||
var passed = 0;
|
||||
seoChecks.forEach(function (chk) {
|
||||
var ok = chk.check();
|
||||
if (ok) passed++;
|
||||
seoDots[chk.id].className = 'mokoog-seo-dot ' + (ok ? 'mokoog-seo-pass' : 'mokoog-seo-fail');
|
||||
});
|
||||
|
||||
seoTotal.textContent = passed + '/' + seoChecks.length + ' checks passed';
|
||||
|
||||
if (passed === seoChecks.length) {
|
||||
seoTotal.className = 'mokoog-seo-total mokoog-seo-total-good';
|
||||
} else if (passed >= Math.ceil(seoChecks.length / 2)) {
|
||||
seoTotal.className = 'mokoog-seo-total mokoog-seo-total-ok';
|
||||
} else {
|
||||
seoTotal.className = 'mokoog-seo-total mokoog-seo-total-bad';
|
||||
}
|
||||
}
|
||||
|
||||
Object.values(fields).forEach(function (el) {
|
||||
if (el) {
|
||||
el.addEventListener('input', updatePreview);
|
||||
el.addEventListener('change', updatePreview);
|
||||
el.addEventListener('input', function () { updatePreview(); updateSeoScore(); });
|
||||
el.addEventListener('change', function () { updatePreview(); updateSeoScore(); });
|
||||
}
|
||||
});
|
||||
|
||||
if (fields.ogImage) {
|
||||
var observer = new MutationObserver(updatePreview);
|
||||
var observer = new MutationObserver(function () { updatePreview(); updateSeoScore(); });
|
||||
observer.observe(fields.ogImage, { attributes: true, attributeFilter: ['value'] });
|
||||
}
|
||||
|
||||
updatePreview();
|
||||
updateSeoScore();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user