Expand my Community achievements bar.

AEM 6.5.12.0 - Extend RTE Link Plugin Command to add Icon / Info text specifying doc type | AEM Community Blog Seeding

Avatar

Administrator

8/3/22

BlogImage.jpg

AEM 6.5.12.0 - Extend RTE Link Plugin Command to add Icon / Info text specifying doc type by Sreekanth Choudry Nalabotu

Abstract

Goal
Extend the RTE Link Command (/libs/clientlibs/granite/coralui2/optional/rte/js/core/commands/link.js) to automatically add html specifying document type (eg. PDF, Word etc.)

Solution
Create clientlib /apps/eaem-rte-anchor-link-html/clientlib with categories=[cq.authoring.dialog.all], add the following code..

(function ($, $document) {
const RICH_TEXT_EDITABLE_SELECTOR = ".cq-RichText-editable",
DATA_RTE_INSTANCE = "rteinstance";

$document.on("foundation-contentloaded", function(e){
const $richTextDiv = $(e.target).find(RICH_TEXT_EDITABLE_SELECTOR);

$richTextDiv.each(function() {
$(this).on("editing-start", function() {
const $this = $(this),
rte = $this.data(DATA_RTE_INSTANCE),
ek = rte.editorKernel;

extendLinkCommand(ek.registeredCommands._link);
})
});
});

function extendLinkCommand(_linkCmd){
const origAddLinkToDomFn = _linkCmd.addLinkToDom;

_linkCmd.addLinkToDom = function(execDef){
origAddLinkToDomFn.call(this, execDef);

const context = execDef.editContext,
path = execDef.value.url;

if(path.endsWith(".pdf")){
addIconHTML(context, getPDFIconHTML());
}else if(path.endsWith(".docx")){
addIconHTML(context, getWordIconHTML());
}else{
addIconHTML(context, getFileIconHTML());
}
}

function addIconHTML(context, iconHTML){
let range = CUI.rte.Selection.getLeadRange(context);

let tempDiv = context.doc.createElement("div");
tempDiv.innerHTML = iconHTML;

let textFrag = context.doc.createDocumentFragment();
let firstNode, lastNode;

while ((firstNode = tempDiv.firstChild)) {
lastNode = textFrag.appendChild(firstNode);
}

range.deleteContents();
range.insertNode(textFrag);
range.setStartAfter(lastNode);
}

function getPDFIconHTML(){
return ' (pdf)';
}

function getWordIconHTML(){
return ' (word)';
}

function getFileIconHTML(){
return ' (document)';
}
}

}(jQuery, jQuery(document)));

Read Full Blog

AEM 6.5.12.0 - Extend RTE Link Plugin Command to add Icon / Info text specifying doc type

Q&A

Please use this thread to ask the related questions.

5 Comments

Avatar

Level 1

7/11/24

(function ($) {
var FootnotePlugin = new Class({
extend: CUI.rte.plugins.Plugin,
toString: "FootnotePlugin",

initializeUI: function (editor) {
var group = editor.ui.toolbar.getGroup("misc");
var button = group.getToolbar().addButton("footnotebutton", {
icon: "link",
title: "Insert Footnote",
command: "insertfootnote"
});
button.on("click", this.handleInsertFootnote.bind(this));
},

handleInsertFootnote: function () {
var editor = this.editorKernel;

// Create a dialog for footnote input
var dialog = new Coral.Dialog();
dialog.header.innerHTML = "Insert Footnote";
dialog.content.innerHTML = `
<form class="coral-Form">
<coral-panelstack>
<coral-panel>
<coral-panel-content>
<div class="coral-Form-fieldwrapper">
<label class="coral-Form-fieldlabel" for="footnoteText">Footnote Text</label>
<input is="coral-textfield" id="footnoteText" class="coral-Form-field" type="text">
</div>
<div class="coral-Form-fieldwrapper">
<label class="coral-Form-fieldlabel" for="footnotePath">Footnote Path</label>
<input is="coral-textfield" id="footnotePath" class="coral-Form-field" type="text">
</div>
<div class="coral-Form-fieldwrapper">
<input is="coral-checkbox" id="footnoteCheckbox">
<label class="coral-Form-fieldlabel" for="footnoteCheckbox">Enable Path</label>
</div>
</coral-panel-content>
</coral-panel>
</coral-panelstack>
</form>
`;
dialog.footer.innerHTML = `
<button is="coral-button" variant="primary" coral-close>Insert</button>
<button is="coral-button" variant="default" coral-close>Cancel</button>
`;
document.body.appendChild(dialog);

// Handle dialog submit
dialog.on('click', 'button[variant="primary"]', function() {
var footnoteText = dialog.querySelector('#footnoteText').value.trim();
var footnotePath = dialog.querySelector('#footnotePath').value.trim();
var enablePath = dialog.querySelector('#footnoteCheckbox').checked;

if (footnoteText) {
var footnoteHtml = `<sup class="footnote">${footnoteText}`;
if (enablePath && footnotePath) {
footnoteHtml = `<a href="${footnotePath}" target="_blank">${footnoteHtml}</a>`;
}
footnoteHtml += `</sup>`;
editor.relayCmd("insertHtml", footnoteHtml);
}

dialog.hide();
document.body.removeChild(dialog);
});

dialog.show();
}
});

CUI.rte.plugins.PluginRegistry.register("footnote", FootnotePlugin);
})(jQuery);

Avatar

Level 1

7/16/24

this answer might work


function initializeCheckboxInDialog() {
// Check if the path container already has the checkbox to prevent duplication
if (document.getElementById('togglePathCheckbox')) return;

const checkboxLabel = document.createElement('label');
checkboxLabel.style.paddingLeft = '10px';
checkboxLabel.innerHTML = `
<input type="checkbox" id="togglePathCheckbox"> Show Footnote Path
`;

const pathContainer = document.querySelector('.rte-dialog-columnContainer');
if (pathContainer) {
pathContainer.insertBefore(checkboxLabel, pathContainer.firstChild);

const pathInputContainer = pathContainer.querySelector('.rte-dialog-column');
if (pathInputContainer) {
const additionalPathInputContainer = pathInputContainer.cloneNode(true);
additionalPathInputContainer.style.display = 'none'; // Initially hide the additional path input container

// Update the label and placeholder for the cloned path input container
const label = additionalPathInputContainer.querySelector('label');
if (label) {
label.textContent = 'Footnote Path';
}

const input = additionalPathInputContainer.querySelector('input[is="coral-textfield"]');
if (input) {
input.placeholder = 'Footnote Path';
}

pathContainer.insertBefore(additionalPathInputContainer, pathInputContainer);

const togglePathCheckbox = document.getElementById('togglePathCheckbox');
togglePathCheckbox.addEventListener('change', function() {
if (togglePathCheckbox.checked) {
additionalPathInputContainer.style.display = 'flex'; // Show the additional path input container
} else {
additionalPathInputContainer.style.display = 'none'; // Hide the additional path input container
}
});
}
}
}

// Function to detect when the dialog is opened
function onDialogOpen() {
initializeCheckboxInDialog();
}

// Add an event listener to the button click event
const buttonElement = document.querySelector('[data-action="links#modifylink"]');
if (buttonElement) {
buttonElement.addEventListener('click', function() {
// Wait for the dialog to be fully rendered and opened
setTimeout(onDialogOpen, 500); // Adjust the delay as necessary
});
}

Avatar

Level 1

7/17/24

latest code reading 



(function (document) {
"use strict";

function initializeCheckboxInDialog() {
const linkDialog = document.querySelector('[data-rte-dialog="link"]');

if (linkDialog && !document.getElementById('customCheckbox')) {
const checkboxContainer = document.createElement("div");
checkboxContainer.className = "rte-dialog-columnContainer";
checkboxContainer.style.display = "flex";
checkboxContainer.style.flexDirection = "column";

const checkboxLabel = document.createElement("label");
checkboxLabel.style.paddingLeft = "10px";
checkboxLabel.innerText = "Enable Path Field";

const checkboxInput = document.createElement("input");
checkboxInput.type = "checkbox";
checkboxInput.id = "customCheckbox";

checkboxLabel.appendChild(checkboxInput);
checkboxContainer.appendChild(checkboxLabel);

const pathFieldContainer = document.createElement("div");
pathFieldContainer.className = "rte-dialog-columnContainer";
pathFieldContainer.style.display = "none";
pathFieldContainer.style.flexDirection = "column";

const pathFieldLabel = document.createElement("label");
pathFieldLabel.style.paddingLeft = "10px";
pathFieldLabel.innerText = "Footnote Path";

const pathFieldInput = document.createElement("div");
pathFieldInput.setAttribute('id', 'footnotepath');
pathFieldInput.className = "rte-dialog-column";
pathFieldInput.innerHTML = `
<foundation-autocomplete pickersrc="/mnt/overlay/cq/gui/content/linkpathfield/picker.html{value}" placeholder="Footnote Path" name="customPathField">
<div class="foundation-autocomplete-inputgroupwrapper">
<div class="coral-InputGroup" role="presentation">
<input is="textfield" class="coral-InputGroup-input _coral-Textfield" autocomplete="off" placeholder="Path" id="customPathFieldInput" role="combobox" aria-expanded="false" variant="default" aria-label="Path">
<span class="coral-InputGroup-button">
<button is="coral-button" title="Open Selection Dialog" type="button" aria-label="Open Selection Dialog" variant="default" class="_coral-Button _coral-Button--primary" size="M" icon="FolderOpenOutline">
<coral-icon size="S" class="_coral-Icon--sizeS _coral-Icon" role="img" icon="FolderOpenOutline" aria-hidden="true">
<svg focusable="false" aria-hidden="true" class="_coral-Icon--svg _coral-Icon">
<use xlink:href="#spectrum-icon-18-FolderOpenOutline"></use>
</svg>
</coral-icon>
<coral-button-label class="_coral-Button-label"></coral-button-label>
</button>
</span>
</div>
</div>
<coral-overlay class="foundation-autocomplete-value foundation-picker-buttonlist _coral-Overlay" data-foundation-picker-buttonlist-src="/mnt/overlay/cq/gui/content/linkpathfield/suggestion{.offset,limit}.html?root=/content&filter=hierarchyNotFile{&query}" style="display: none; visibility: visible;"></coral-overlay>
<coral-taglist foundation-autocomplete-value="" name="customPathField" class="_coral-Tags" aria-live="off" aria-atomic="false" aria-relevant="additions" role="grid">
<coral-tag closable="" class="_coral-Tags-item _coral-Tags-item--deletable" value="" size="S" color="" role="row" aria-labelledby="coral-id-860" tabindex="0">
<coral-tag-label class="_coral-Tags-itemLabel" role="rowheader" id="coral-id-860"></coral-tag-label>
<input id="customfield" type="hidden" handle="input" name="customPathField" value="">
<span handle="buttonCell" role="gridcell">
<button is="coral-button" tracking="off" handle="button" type="button" variant="action" class="_coral-ClearButton _coral-ClearButton--small _coral-Tags-item-removeButton _coral-ActionButton" title="Remove" tabindex="0" coral-close="" aria-label="Remove: " size="M">
<svg focusable="false" aria-hidden="true" class="_coral-Icon--svg _coral-Icon _coral-UIIcon-CrossSmall">
<use xlink:href="#spectrum-css-icon-CrossSmall"></use>
</svg>
<coral-button-label handle="buttonLabel" class="_coral-ActionButton-label"></coral-button-label>
</button>
</span>
</coral-tag>
</coral-taglist>
</foundation-autocomplete>
`;

const pathFieldElement = pathFieldInput.querySelector('input[is="coral-textfield"]');
pathFieldElement.id = "customPathFieldInput";

pathFieldElement.addEventListener('foundation-autocomplete:valuechange', function (event) {
const selectedValue = event.target.value;
if (selectedValue) {
pathFieldElement.setAttribute("value", selectedValue);
}
});

pathFieldLabel.appendChild(pathFieldInput);
pathFieldContainer.appendChild(pathFieldLabel);
checkboxContainer.appendChild(pathFieldContainer);

checkboxInput.addEventListener("change", function () {
if (checkboxInput.checked) {
pathFieldContainer.style.display = "flex";
} else {
pathFieldContainer.style.display = "none";
}
});

linkDialog.insertBefore(checkboxContainer, linkDialog.firstChild);
}
}

const observer = new MutationObserver(function (mutations) {
mutations.forEach(function (mutation) {
if (mutation.addedNodes.length) {
mutation.addedNodes.forEach(function (node) {
if (node.nodeType === 1 && node.matches('[data-rte-dialog="link"]')) {
initializeCheckboxInDialog();
}
});
}
});
});

const observerConfig = {
childList: true,
subtree: true
};

observer.observe(document.body, observerConfig);

function getCustomPathFieldValue() {
const customPathFieldInput = document.getElementById('customPathFieldInput');
if (customPathFieldInput) {
return customPathFieldInput.value;
} else {
return null;
}
}

document.addEventListener('click', function () {
const pathValue = getCustomPathFieldValue();
if (pathValue) {
console.log('Custom Path Field Value:', pathValue);
}
});

})(document);

Avatar

Level 1

7/31/24

if (this.config.linkDialogConfig) {
var addDialogConfig = this.config.linkDialogConfig;
if (addDialogConfig.linkAttributes) {
com.removeJcrData(addDialogConfig.linkAttributes);
var linkAttribs = com.toArray(addDialogConfig.linkAttributes);
dialogConfig.additionalFields = [];
var attribCnt = linkAttribs.length;
var fromModel = function (obj, field) {
if (dialogHelper.getItemType(field) === dh.TYPE_HIDDEN) {
return;
}
var attribName = dialogHelper.getItemName(field);
var attribValue = com.getAttribute(obj.dom, attribName);
if (attribValue) {
dialogHelper.setItemValue(field, attribValue);
} else {
dialogHelper.setItemValue(field, '');
}
};
var toModel = function (obj, field) {
var attribName = dialogHelper.getItemName(field);
if (!obj.attributes) {
obj.attributes = {};
}
var value = dialogHelper.getItemValue(field);
if (value && (value.length > 0)) {
obj.attributes[attribName] = value;
} else {
obj.attributes[attribName] =
CUI.rte.commands.Link.REMOVE_ATTRIBUTE;
}
};
for (var a = 0; a < attribCnt; a++) {
var attrib = linkAttribs[a];
var type = attrib.type || attrib.xtype;
var attribName = attrib.attribute;
var attribLabel = attrib.label || attrib.fieldLabel;
var itemData = {
'item': dialogHelper.createItem(type, attribName, attribLabel),
'fromModel': fromModel,
'toModel': toModel
};
delete attrib.attribute;
delete attrib.type;
delete attrib.xtype;
delete attrib.label;
delete attrib.fieldLabel;
CUI.rte.Utils.applyDefaults(itemData.item, attrib);
dialogConfig.additionalFields.push(itemData);
}
// Adding another footnote path input field
var additionalFootnotePathItem = dialogHelper.createItem(dh.TYPE_TEXTFIELD, 'additionalFootnotePath',
'Additional Footnote Path');
dialogConfig.additionalFields.push({
'item': additionalFootnotePathItem,
'fromModel': function (obj, field) {
if (obj.attributes && obj.attributes['additionalFootnotePath']) {
dialogHelper.setItemValue(field, obj.attributes['additionalFootnotePath']);
}
},
'toModel': function (obj, field) {
var value = dialogHelper.getItemValue(field);
if (!obj.attributes) {
obj.attributes = {};
}
if (value && (value.length > 0)) {
obj.attributes['additionalFootnotePath'] = value;
} else {
obj.attributes['additionalFootnotePath'] = null;
}
}
});
delete addDialogConfig.linkAttributes;
}
CUI.rte.Utils.applyDefaults(dialogConfig.dialogProperties, addDialogConfig);
}

 

 

 

 

----------

 

applyLink: function (context) {
console.log("entered");
var linkObj = this.linkDialog.objToEdit;

if (linkObj) {
var linkUrl = linkObj.href;
var cssClass = linkObj.cssClass;
var target = linkObj.target;

// Retrieve additional footnote path
var additionalFootnotePath = this.linkDialog.dialogHelper.getItemValue('additionalFootnotePath');
if (!linkObj.attributes) {
linkObj.attributes = {};
}
linkObj.attributes['additionalFootnotePath'] = additionalFootnotePath;

CUI.rte.Selection.restoreNativeSelection(context, this.savedRange);
this.editorKernel.relayCmd('modifylink', {
'url': linkUrl,
'css': cssClass,
'target': target,
'trimLinkSelection': this.config.trimLinkSelection,
'attributes': linkObj.attributes
});
}
}

Avatar

Level 1

8/4/24

const customPathFieldInput = document.getElementById('customPathFieldInput');
const footnotePath = customPathFieldInput ? customPathFieldInput.value : null;
console.log(customPathFieldInput.value)

const rteEditable = document.querySelector('.cq-RichText-editable');
if (rteEditable) {
const anchorTags = rteEditable.querySelectorAll('a');
anchorTags.forEach(function (anchor) {
if (footnotePath) {
anchor.setAttribute('data-footnote-path', footnotePath);
} else {
anchor.removeAttribute('data-footnote-path');
}
});
}