Skip to content

Commit 30689d6

Browse files
committed
feat : added script for OpenGraph placeholder in build folder
1 parent 8a9441b commit 30689d6

File tree

7 files changed

+205
-17
lines changed

7 files changed

+205
-17
lines changed

.gitignore

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,5 +107,3 @@ cypress.config.mjs
107107

108108
# WebAuthn FIDO metadata cache
109109
/plugins/system/webauthn/fido.jwt
110-
administrator/language/en-GB/en-GB.plg_system_cccsocialmedia.ini
111-
administrator/language/en-GB/en-GB.plg_system_cccsocialmedia.sys.ini
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"$schema": "https://developer.joomla.org/schemas/json-schema/web_assets.json",
3+
"name": "plg_system_opengraph",
4+
"version": " __DEPLOY_VERSION__",
5+
"description": "Joomla CMS",
6+
"license": "GPL-2.0-or-later",
7+
"assets": [
8+
{
9+
"name": "plg_system_opengraph.opengraph-placeholder",
10+
"type": "script",
11+
"uri": "plg_system_opengraph/opengraph-placeholder.min.js",
12+
"dependencies": ["core"],
13+
"attributes": {
14+
"type": "module"
15+
}
16+
}
17+
]
18+
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
document.addEventListener("DOMContentLoaded", () => {
2+
const maps = Joomla.getOptions("plgOgMappings", {});
3+
4+
const selectorFor = (token) => {
5+
if (token.startsWith("field.")) {
6+
const n = CSS.escape(token.slice(6));
7+
return `[name="jform[com_fields][${n}]"]`;
8+
}
9+
if (token.startsWith("image_")) {
10+
return `[name="jform[images][${CSS.escape(token)}]"]`;
11+
}
12+
return `[name="jform[${CSS.escape(token)}]"]`;
13+
};
14+
15+
function getSourceName(token) {
16+
if (token.startsWith("field.")) {
17+
const fieldKey = token.slice(6).replace(/_/g, " ");
18+
return `Custom Field: ${fieldKey}`;
19+
}
20+
21+
const mapCoreNames = {
22+
title: "Title",
23+
alias: "Alias",
24+
metadesc: "Meta Description",
25+
metakey: "Meta Keywords",
26+
articletext: "Article Text",
27+
image_intro: "Intro Image",
28+
image_intro_alt: "Intro Image Alt",
29+
image_fulltext: "Fulltext Image",
30+
image_fulltext_alt: "Fulltext Image Alt",
31+
created_by_alias: "Author Alias",
32+
};
33+
34+
return mapCoreNames[token] || token;
35+
}
36+
37+
function sanitizeText(input, maxLen = 60) {
38+
if (typeof input !== "string" || !input.trim()) return "";
39+
40+
// Remove HTML tags manually (just in case innerText fails)
41+
const noTags = input.replace(/<[^>]*>/g, " ");
42+
43+
// Decode HTML entities using a temporary div
44+
const tempDiv = document.createElement("div");
45+
tempDiv.innerHTML = noTags;
46+
const decoded = tempDiv.textContent || tempDiv.innerText || "";
47+
48+
// Normalize whitespace
49+
const cleaned = decoded.replace(/\s+/g, " ").trim();
50+
51+
// Truncate with word boundary safety
52+
if (cleaned.length <= maxLen) return cleaned;
53+
54+
const cut = cleaned.lastIndexOf(" ", maxLen - 1);
55+
const safeCut = cut > maxLen * 0.6 ? cut : maxLen - 1;
56+
return cleaned.slice(0, safeCut).replace(/[.,;:\-\s]+$/, "") + "…";
57+
}
58+
59+
const maxLen = {
60+
og_title: Number(maps.maxTitleLen) || 60,
61+
og_description: Number(maps.maxDescLen) || 160,
62+
og_image_alt: Number(maps.maxAltLen) || 125,
63+
};
64+
65+
Object.entries(maps).forEach(([ogKey, token]) => {
66+
const ogInput = document.getElementById(`jform_attribs_${ogKey}`);
67+
const srcInput = document.querySelector(selectorFor(token));
68+
69+
if (!ogInput || !srcInput) return;
70+
71+
const originalPh = ogInput.placeholder;
72+
const inherited = Joomla.Text._("PLG_SYSTEM_OPENGRAPH_INHERITED");
73+
const paint = () => {
74+
if (ogInput.value.trim()) return; // user override
75+
let v = srcInput.value.trim();
76+
77+
if (ogKey !== "og_image") {
78+
v = sanitizeText(v, maxLen[ogKey]);
79+
}
80+
const sourceLabel = getSourceName(token);
81+
ogInput.placeholder = v
82+
? `${v}${inherited} from ${sourceLabel}`
83+
: originalPh;
84+
};
85+
86+
paint(); // initial
87+
srcInput.addEventListener("input", paint);
88+
89+
ogInput.addEventListener("input", () => {
90+
ogInput.placeholder = originalPh; // detach
91+
srcInput.removeEventListener("input", paint);
92+
});
93+
});
94+
});

libraries/src/Opengraph/OpengraphServiceInterface.php

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,44 @@
1313
\defined('_JEXEC') or die;
1414
// phpcs:enable PSR1.Files.SideEffects
1515

16+
17+
18+
/**
19+
* Enumerates the logical OpenGraph groups that a custom field-type can
20+
* register itself under.
21+
*
22+
* Third-party field-type plugins should return one of these cases from
23+
* {@see MappableFieldInterface::getOpengraphGroup()} so the System – OpenGraph
24+
* plugin knows how to categorise the field inside its mapping drop-down.
25+
*
26+
* @since __DEPLOY_VERSION__
27+
*/
28+
enum OpengraphGroup: string
29+
{
30+
/** Standard textual content (e.g. single-line, multi-line). */
31+
case TEXT = 'text-fields';
32+
33+
/** Image or media file (intro/full images, custom media fields, …). */
34+
case IMAGE = 'image-fields';
35+
36+
/** Alternate-text associated with an image (accessibility / SEO). */
37+
case IMAGE_ALT = 'image-alt-fields';
38+
39+
// TODO : implement these cases in the future, if needed
40+
41+
// /** Meta-information such as description or keywords. */
42+
// case META = 'meta-fields';
43+
44+
// /** Locale or language codes (e.g. `en-US`). */
45+
// case LOCALE = 'locale-fields';
46+
47+
// /** Author-related data (creator, modifier, alias, …). */
48+
// case AUTHOR = 'author-fields';
49+
50+
// /** Date-time values (created, modified, publish-up/down, …). */
51+
// case DATE = 'date-fields';
52+
}
53+
1654
/**
1755
* The Opengraph service.
1856
*
@@ -26,6 +64,22 @@ interface OpengraphServiceInterface
2664
* @return array
2765
*
2866
* @since __DEPLOY_VERSION__
67+
*
2968
*/
3069
public function getOpengraphFields(): array;
3170
}
71+
72+
73+
74+
75+
interface MappableFieldInterface
76+
{
77+
/**
78+
* Returns the OpenGraph group this field should be listed under.
79+
*
80+
* @return OpengraphGroup One of the enum cases defined in {@see OpengraphGroup}.
81+
*
82+
* @since __DEPLOY_VERSION__
83+
*/
84+
public function getOpengraphGroup(): OpengraphGroup;
85+
}

plugins/system/opengraph/opengraph.xml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@
1313
<version>__DEPLOY_VERSION__</version>
1414
<description>PLG_SYSTEM_OPENGRAPH_DESCRIPTION</description>
1515
<namespace path="src">Joomla\Plugin\System\Opengraph</namespace>
16+
<media destination="plg_system_opengraph"
17+
folder="media">
18+
<folder>js</folder>
19+
<filename>joomla.asset.json</filename>
20+
</media>
1621
<files>
1722
<folder plugin="opengraph">src</folder>
1823
<folder>services</folder>
@@ -82,4 +87,4 @@
8287
</fieldset>
8388
</fields>
8489
</config>
85-
</extension>
90+
</extension>

plugins/system/opengraph/src/Extension/opengraph.php

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -191,15 +191,8 @@ public function onContentPrepareForm(PrepareFormEvent $event): void
191191
/** @var WebAssetManager $wa */
192192
$wa = $document->getWebAssetManager();
193193

194-
$wa->registerAndUseScript(
195-
'plg.opengraph.placeholder',
196-
'media/plg_system_opengraph/js/opengraph-placeholder.js',
197-
[
198-
'type' => 'module',
199-
'version' => 'auto',
200-
'dependencies' => ['core'],
201-
]
202-
);
194+
$wa->getRegistry()->addExtensionRegistryFile('plg_system_opengraph');
195+
$wa->useScript('plg_system_opengraph.opengraph-placeholder');
203196
}
204197

205198

plugins/system/opengraph/src/Field/OpengraphField.php

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,10 @@
1414
use Joomla\CMS\Form\Field\GroupedlistField;
1515
use Joomla\CMS\HTML\HTMLHelper;
1616
use Joomla\CMS\Language\Text;
17+
use Joomla\CMS\Opengraph\MappableFieldInterface;
18+
use Joomla\CMS\Opengraph\OpengraphGroup;
1719
use Joomla\CMS\Opengraph\OpengraphServiceInterface;
20+
use Joomla\CMS\Plugin\PluginHelper;
1821
use Joomla\Component\Fields\Administrator\Helper\FieldsHelper;
1922

2023
// phpcs:disable PSR1.Files.SideEffects
@@ -83,12 +86,12 @@ protected function getGroups()
8386

8487
// Allowed field types for each OpenGraph group
8588
$allowedFieldTypes = [
86-
'text-fields' => ['text', 'textarea'],
87-
'image-fields' => ['media', 'imagelist'],
88-
'image-alt-fields' => ['text'],
89+
OpengraphGroup::TEXT->value => ['text', 'textarea'],
90+
OpengraphGroup::IMAGE->value => ['media', 'imagelist'],
91+
OpengraphGroup::IMAGE_ALT->value => ['text'],
8992
];
9093

91-
$allowedTypes = $allowedFieldTypes[$fieldType] ?? [];
94+
$nativeTypes = $allowedFieldTypes[$fieldType] ?? [];
9295

9396

9497

@@ -105,10 +108,33 @@ protected function getGroups()
105108
$customOptions = [];
106109

107110
foreach ($customFields as $field) {
108-
if (!\in_array($field->type, $allowedTypes, true)) {
111+
$accept = \in_array($field->type, $nativeTypes, true);
112+
113+
114+
// If not native-allowed, see if the field’s plugin implements our interface
115+
if (!$accept) {
116+
// Class name convention: PlgFields{Type}
117+
$class = 'PlgFields' . ucfirst($field->type);
118+
119+
// Ensure plugin autoloaded
120+
PluginHelper::importPlugin('fields');
121+
122+
if (
123+
\class_exists($class)
124+
&& \is_subclass_of($class, MappableFieldInterface::class)
125+
&& $class::getOpengraphGroup()->value === $fieldType
126+
) {
127+
$accept = true;
128+
}
129+
}
130+
131+
132+
if (!$accept) {
109133
continue;
110134
}
111135

136+
137+
112138
$label = $field->title . ' (' . $field->name . ')';
113139
$customOptions[] = HTMLHelper::_('select.option', 'field.' . $field->name, $label);
114140
}

0 commit comments

Comments
 (0)