feat: add fediverse:creator tag, character counters, LinkedIn preview
- Add fediverse:creator meta tag for Mastodon/Fediverse author attribution — first extension on any CMS to support this (closes #57) - Add live character count indicators with green/yellow/red color coding on OG title, description, SEO title, and meta description fields in the article/menu editor (closes #58) - Add LinkedIn social preview card alongside existing Facebook and Twitter/X previews in the editor (closes #61)
This commit is contained in:
@@ -15,6 +15,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
||||
- Fix multilingual data corruption in content plugin load/save (#41)
|
||||
|
||||
### Added
|
||||
- Fediverse/Mastodon `fediverse:creator` meta tag — first extension on any CMS to support this (#57)
|
||||
- Live character count indicators on OG title, OG description, SEO title, meta description fields with color-coded warnings (#58)
|
||||
- LinkedIn social preview card in article/menu editor alongside Facebook and Twitter/X previews (#61)
|
||||
- Site-wide default OG title and description plugin parameters
|
||||
- Discord embed color via `theme-color` meta tag (color picker in plugin config)
|
||||
- LinkedIn article tags: `article:published_time`, `article:modified_time`, `article:author`
|
||||
|
||||
@@ -102,3 +102,46 @@
|
||||
color: #536471;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
/* LinkedIn card */
|
||||
.mokoog-card-li {
|
||||
border: 1px solid #e0dfdc;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.mokoog-card-li .mokoog-card-body {
|
||||
border-top-color: #e0dfdc;
|
||||
}
|
||||
|
||||
.mokoog-card-li .mokoog-card-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: rgba(0, 0, 0, 0.9);
|
||||
}
|
||||
|
||||
.mokoog-card-li .mokoog-card-domain {
|
||||
font-size: 12px;
|
||||
color: rgba(0, 0, 0, 0.6);
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
/* Character count indicators */
|
||||
.mokoog-char-count {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
margin-top: 4px;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.mokoog-char-ok {
|
||||
color: #2e7d32;
|
||||
}
|
||||
|
||||
.mokoog-char-warn {
|
||||
color: #f57c00;
|
||||
}
|
||||
|
||||
.mokoog-char-over {
|
||||
color: #d32f2f;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
@@ -15,9 +15,44 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
ogDesc: document.getElementById('jform_mokoog_og_description'),
|
||||
ogImage: document.getElementById('jform_mokoog_og_image'),
|
||||
articleTitle: document.getElementById('jform_title'),
|
||||
metaDesc: document.getElementById('jform_metadesc')
|
||||
metaDesc: document.getElementById('jform_metadesc'),
|
||||
seoTitle: document.getElementById('jform_mokoog_seo_title'),
|
||||
metaDescription: document.getElementById('jform_mokoog_meta_description')
|
||||
};
|
||||
|
||||
// Character count indicators
|
||||
var charLimits = [
|
||||
{ field: fields.ogTitle, optimal: 60, max: 90 },
|
||||
{ field: fields.ogDesc, optimal: 155, max: 200 },
|
||||
{ field: fields.seoTitle, optimal: 60, max: 70 },
|
||||
{ field: fields.metaDescription, optimal: 155, max: 160 }
|
||||
];
|
||||
|
||||
charLimits.forEach(function (cfg) {
|
||||
if (!cfg.field) return;
|
||||
|
||||
var counter = document.createElement('span');
|
||||
counter.className = 'mokoog-char-count';
|
||||
cfg.field.parentNode.appendChild(counter);
|
||||
|
||||
function refresh() {
|
||||
var len = cfg.field.value.length;
|
||||
counter.textContent = len + '/' + cfg.optimal;
|
||||
|
||||
if (len > cfg.max) {
|
||||
counter.className = 'mokoog-char-count mokoog-char-over';
|
||||
} else if (len > cfg.optimal) {
|
||||
counter.className = 'mokoog-char-count mokoog-char-warn';
|
||||
} else {
|
||||
counter.className = 'mokoog-char-count mokoog-char-ok';
|
||||
}
|
||||
}
|
||||
|
||||
cfg.field.addEventListener('input', refresh);
|
||||
cfg.field.addEventListener('change', refresh);
|
||||
refresh();
|
||||
});
|
||||
|
||||
// Find the mokoog fieldset and insert preview after it
|
||||
var fieldset = document.querySelector('[data-showon-id="mokoog"]') ||
|
||||
document.getElementById('attrib-mokoog') ||
|
||||
@@ -110,6 +145,36 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
twCard.appendChild(twBody);
|
||||
wrapper.appendChild(twCard);
|
||||
|
||||
// LinkedIn preview card
|
||||
var liLabel = document.createElement('small');
|
||||
liLabel.className = 'mokoog-platform-label';
|
||||
liLabel.textContent = 'LinkedIn';
|
||||
wrapper.appendChild(liLabel);
|
||||
|
||||
var liCard = document.createElement('div');
|
||||
liCard.className = 'mokoog-card mokoog-card-li';
|
||||
|
||||
var liImg = document.createElement('div');
|
||||
liImg.id = 'mokoog-li-img';
|
||||
liImg.className = 'mokoog-card-img';
|
||||
liCard.appendChild(liImg);
|
||||
|
||||
var liBody = document.createElement('div');
|
||||
liBody.className = 'mokoog-card-body';
|
||||
|
||||
var liTitle = document.createElement('div');
|
||||
liTitle.id = 'mokoog-li-title';
|
||||
liTitle.className = 'mokoog-card-title';
|
||||
liBody.appendChild(liTitle);
|
||||
|
||||
var liDomain = document.createElement('div');
|
||||
liDomain.id = 'mokoog-li-domain';
|
||||
liDomain.className = 'mokoog-card-domain';
|
||||
liBody.appendChild(liDomain);
|
||||
|
||||
liCard.appendChild(liBody);
|
||||
wrapper.appendChild(liCard);
|
||||
|
||||
preview.appendChild(wrapper);
|
||||
fieldset.parentNode.insertBefore(preview, fieldset.nextSibling);
|
||||
|
||||
@@ -152,6 +217,18 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
} else {
|
||||
twImgEl.style.display = 'none';
|
||||
}
|
||||
|
||||
// LinkedIn (shorter truncation: title 70, no description shown in card)
|
||||
var liTitle = title.length > 70 ? title.substring(0, 67) + '...' : title;
|
||||
document.getElementById('mokoog-li-title').textContent = liTitle;
|
||||
document.getElementById('mokoog-li-domain').textContent = domain;
|
||||
var liImgEl = document.getElementById('mokoog-li-img');
|
||||
if (img) {
|
||||
liImgEl.style.backgroundImage = 'url(' + encodeURI(img) + ')';
|
||||
liImgEl.style.display = '';
|
||||
} else {
|
||||
liImgEl.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
Object.values(fields).forEach(function (el) {
|
||||
|
||||
@@ -23,6 +23,8 @@ PLG_SYSTEM_MOKOOG_FIELD_FB_APP_ID="Facebook App ID"
|
||||
PLG_SYSTEM_MOKOOG_FIELD_FB_APP_ID_DESC="Your Facebook App ID for fb:app_id meta tag."
|
||||
PLG_SYSTEM_MOKOOG_FIELD_DISCORD_COLOR="Discord Embed Color"
|
||||
PLG_SYSTEM_MOKOOG_FIELD_DISCORD_COLOR_DESC="The color of the embed sidebar when shared on Discord. Sets the theme-color meta tag. Leave blank to use Discord defaults."
|
||||
PLG_SYSTEM_MOKOOG_FIELD_FEDIVERSE_CREATOR="Fediverse Creator"
|
||||
PLG_SYSTEM_MOKOOG_FIELD_FEDIVERSE_CREATOR_DESC="Your Fediverse/Mastodon handle (e.g. @user@mastodon.social). Outputs a fediverse:creator meta tag for author attribution on Mastodon and other Fediverse platforms."
|
||||
PLG_SYSTEM_MOKOOG_FIELD_AUTO_GENERATE="Auto-generate Tags"
|
||||
PLG_SYSTEM_MOKOOG_FIELD_AUTO_GENERATE_DESC="Automatically generate OG tags from article content when no custom tags are set."
|
||||
PLG_SYSTEM_MOKOOG_FIELD_STRIP_HTML="Strip HTML from Description"
|
||||
|
||||
@@ -23,6 +23,8 @@ PLG_SYSTEM_MOKOOG_FIELD_FB_APP_ID="Facebook App ID"
|
||||
PLG_SYSTEM_MOKOOG_FIELD_FB_APP_ID_DESC="Your Facebook App ID for fb:app_id meta tag."
|
||||
PLG_SYSTEM_MOKOOG_FIELD_DISCORD_COLOR="Discord Embed Color"
|
||||
PLG_SYSTEM_MOKOOG_FIELD_DISCORD_COLOR_DESC="The color of the embed sidebar when shared on Discord. Sets the theme-color meta tag. Leave blank to use Discord defaults."
|
||||
PLG_SYSTEM_MOKOOG_FIELD_FEDIVERSE_CREATOR="Fediverse Creator"
|
||||
PLG_SYSTEM_MOKOOG_FIELD_FEDIVERSE_CREATOR_DESC="Your Fediverse/Mastodon handle (e.g. @user@mastodon.social). Outputs a fediverse:creator meta tag for author attribution on Mastodon and other Fediverse platforms."
|
||||
PLG_SYSTEM_MOKOOG_FIELD_AUTO_GENERATE="Auto-generate Tags"
|
||||
PLG_SYSTEM_MOKOOG_FIELD_AUTO_GENERATE_DESC="Automatically generate OG tags from article content when no custom tags are set."
|
||||
PLG_SYSTEM_MOKOOG_FIELD_STRIP_HTML="Strip HTML from Description"
|
||||
|
||||
@@ -106,6 +106,14 @@
|
||||
description="PLG_SYSTEM_MOKOOG_FIELD_DISCORD_COLOR_DESC"
|
||||
default=""
|
||||
/>
|
||||
<field
|
||||
name="fediverse_creator"
|
||||
type="text"
|
||||
label="PLG_SYSTEM_MOKOOG_FIELD_FEDIVERSE_CREATOR"
|
||||
description="PLG_SYSTEM_MOKOOG_FIELD_FEDIVERSE_CREATOR_DESC"
|
||||
default=""
|
||||
filter="string"
|
||||
/>
|
||||
</fieldset>
|
||||
<fieldset name="advanced" label="PLG_SYSTEM_MOKOOG_FIELDSET_ADVANCED">
|
||||
<field
|
||||
|
||||
@@ -177,6 +177,13 @@ final class MokoOG extends CMSPlugin implements SubscriberInterface
|
||||
$doc->setMetaData('theme-color', $discordColor);
|
||||
}
|
||||
|
||||
// Fediverse/Mastodon creator attribution
|
||||
$fediverseCreator = $this->params->get('fediverse_creator', '');
|
||||
|
||||
if ($fediverseCreator) {
|
||||
$doc->setMetaData('fediverse:creator', $fediverseCreator);
|
||||
}
|
||||
|
||||
// LinkedIn article tags
|
||||
if ($option === 'com_content' && $view === 'article' && $id > 0) {
|
||||
$doc->setMetaData('article:published_time', $this->getArticleDate($id, 'publish_up'), 'property');
|
||||
|
||||
Reference in New Issue
Block a user