Yandex Metrika
sanches.free 1 просмотр

Добавляем пользовательские кнопки в HTML-редактор 1С-Битрикс

Структура файлов в /local

Подходит для редакционного режима страниц и инфоблоков там, где открывается стандартный BXHtmlEditor. Создайте каталог /local/php_interface/handlers/htmleditor, внутри файл инициализации PHP и подкаталог assets с script.js и иконками .png примерно 20×20 пикселей. Путь к svg при желании тоже работает; главное — стабильный URL от корня сайта.

Подключение скриптов редактору (D7)

Вместо устаревшего вызова AddEventHandler можно повесить обработчик на модуль fileman через API ядра. Один статический предохранитель не даст выполнить вывод несколько раз за один запрос.

addEventHandler(
    'fileman',
    'OnBeforeHTMLEditorScriptRuns',
    'cfxEmitHtmlEditorExtras'
);

function cfxEmitHtmlEditorExtras()
{
    static $cfxVsEditorPayloadSent = false;
    if ($cfxVsEditorPayloadSent) {
        return;
    }
    $cfxVsEditorPayloadSent = true;

    $cfxAddonScriptPath = __DIR__ . '/assets/script.js';
    if (!is_readable($cfxAddonScriptPath)) {
        return;
    }

    echo '<script>(function(){' . file_get_contents($cfxAddonScriptPath) . '})();</script>';
}

Сохраните PHP во внутреннем файле, например /local/php_interface/handlers/htmleditor/handler.php, чтобы __DIR__ указывал на ту же директорию, что и assets. Регистрацию через EventManager достаточно выполнить из этого же файла, а файл подключите из init.php одним require_once.

Простая кнопка: обёртка вокруг выделения

Метод this.editor.action.actions.formatInline.exec принимает сигнатуру вида (command, value, tagName, arStyle, cssClass, params). Поля command и value в частых сценариях можно заполнить фиктивными строками: решает имя тега.

Если фрагмент уже обёрнут тем же контейнером, повторный клик снимает форматирование — поведение штатное для inline-активаторов редактора.

function cfxToolbarAttachWrapTag(editor) {
    editor.AddButton({
        id: 'cfx_wrap_abbr',
        src: '/local/php_interface/handlers/htmleditor/assets/img/abbr.png',
        name: 'Обёртка abbr для выделения',
        title: 'Подсказка/аббревиатура (abbr)',
        show_name: false,
        handler: function () {
            this.editor.action.actions.formatInline.exec('abbr', null, 'abbr');
        },
        toolbarSort: 305
    });
}

if (window.BXHtmlEditor && window.BXHtmlEditor.editors) {
    for (var cfxAid in window.BXHtmlEditor.editors) {
        if (Object.prototype.hasOwnProperty.call(window.BXHtmlEditor.editors, cfxAid)) {
            cfxToolbarAttachWrapTag(window.BXHtmlEditor.Get(cfxAid));
        }
    }
}
BX.addCustomEvent('OnEditorInitedBefore', cfxToolbarAttachWrapTag);

Кнопка с диалогом: вставить фрагмент как текст

Расширим редактор диалогом на базе BXHtmlEditor.Dialog, сохраняем диапазон выделения, по «Сохранить» восстанавливаем закладку и добавляем узел code с классом, чтобы потом его стилизовать внутри iframe. Альтернатива содержимому — InsertHTML(); для точной замены выделения иногда применяют this.editor.action.Exec('insertHTML', html), но на сложной вёрстке результат может различаться, поэтому ниже узловая вставка.

function CfxInsertedCodePopup(editor, params) {
    params = {
        id: 'cfx_markup_block',
        width: 600,
        className: 'bxhtmled-code-dialog'
    };
    CfxInsertedCodePopup.superclass.constructor.apply(this, [editor, params]);
    this.id = 'code_' + this.editor.id;
    this.cfxSuspendedRange = false;
    this.SetContent(this.Build());
    BX.addCustomEvent(this, 'OnDialogSave', BX.proxy(this.Save, this));
}
BX.extend(CfxInsertedCodePopup, window.BXHtmlEditor.Dialog);

CfxInsertedCodePopup.prototype.Build = function () {
    this.cfxTa = BX.create('textarea', {
        props: { style: 'width:95%;min-height:110px;' }
    });
    return this.cfxTa;
};

CfxInsertedCodePopup.prototype.Save = function () {
    if (this.cfxSuspendedRange) {
        this.editor.selection.SetBookmark(this.cfxSuspendedRange);
    }
    var cfxNd = document.createElement('code');
    cfxNd.className = 'cfx_editor_code_chip';
    cfxNd.innerText = this.cfxTa.value;
    this.editor.selection.InsertNode(cfxNd);
    this.cfxTa.value = '';
};

CfxInsertedCodePopup.prototype.Show = function (bxTag, savedRange) {
    this.cfxSuspendedRange = savedRange;
    if (this.cfxSuspendedRange) {
        this.editor.selection.SetBookmark(this.cfxSuspendedRange);
    }
    this.SetTitle('Вставка кода как текста');
    CfxInsertedCodePopup.superclass.Show.apply(this, arguments);
};

window.BXHtmlEditor.dialogs.CfxInsertedCodePopup = CfxInsertedCodePopup;

function cfxBindIframeStyles(editor) {
    BX.addCustomEvent(editor, 'OnIframeInit', function () {
        var cfxIframe = editor.dom.iframeCont.querySelector('iframe');
        if (!cfxIframe || !cfxIframe.contentWindow || !cfxIframe.contentWindow.document) {
            return;
        }
        var cfxHead = cfxIframe.contentWindow.document.querySelector('head');
        if (!cfxHead) {
            return;
        }
        var cfxLink = document.createElement('link');
        cfxLink.rel = 'stylesheet';
        cfxLink.href = '/local/templates/.default/visual_editor.css';
        cfxHead.appendChild(cfxLink);
    });
}

function cfxToolbarAttachSnippetButton(editor) {
    editor.AddButton({
        id: 'cfx_snippet',
        src: '/local/php_interface/handlers/htmleditor/assets/img/snippet.png',
        name: 'Вставить код',
        codeEditorMode: true,
        handler: function () {
            this.editor.GetDialog('CfxInsertedCodePopup').Show(false, this.savedRange);
        },
        toolbarSort: 225
    });
}

function cfxOnSnippetEditorReady(editor) {
    cfxToolbarAttachSnippetButton(editor);
    cfxBindIframeStyles(editor);
}

if (window.BXHtmlEditor && window.BXHtmlEditor.editors) {
    for (var cfxBid in window.BXHtmlEditor.editors) {
        if (Object.prototype.hasOwnProperty.call(window.BXHtmlEditor.editors, cfxBid)) {
            cfxOnSnippetEditorReady(window.BXHtmlEditor.Get(cfxBid));
        }
    }
}
BX.addCustomEvent('OnEditorInitedBefore', cfxOnSnippetEditorReady);

Обработчик cfxOnSnippetEditorReady сразу вешает кнопку и подключает таблицу стилей внутри документа iframe. Имя в GetDialog() совпадает с ключом в window.BXHtmlEditor.dialogs.

Стили только внутри визуального редактора

Файл /local/templates/.default/visual_editor.css подтягивается в <head> документа iframe при событии OnIframeInit, поэтому внешний сайт его не загружает, а автор сразу видит оформление вставленных блоков.

.cfx_editor_code_chip {
    border: 1px dotted #d97706;
    padding: 3px 6px;
    margin: 4px 0;
    display: block;
    position: relative;
    padding-top: 12px;
}

.cfx_editor_code_chip:before {
    content: "CODE";
    position: absolute;
    top: -6px;
    left: 6px;
    background: #fff;
    font-size: 11px;
    line-height: 1;
}

На что обратить внимание при внедрении

  • Иконки должны быть лёгкими и доступными по указанному пути без редиректов.
  • Если кнопок несколько, вынесите общий сканер уже созданных экземпляров в одну функцию, чтобы не дублировать цикл по BXHtmlEditor.editors.
  • При сборке несколькими авторами имеет смысл хранить script.js под версионированием отдельно от иконок, а в проде включать минификацию.

Не хотите копаться сами?

Починю за 1-3 дня. Без предоплаты — оплата по результату.

15+ лет опыта с 1С-Битрикс · Без предоплаты · 7 дней гарантии