Бесплатно
Загрузите дополнение из админки вашего сайта.
Как загрузить?
Как загрузить?
Версия
1.0.8-rc
Дата выпуска
12.06.2026
Загрузки
0
Просмотры
5
Внимание, этот компонент несовместим с MODX 3.
НЕ РАБОТАЕТ С MODX 3 Пока
mpcVisualEditor (mpcVE)
Вступление
Визуальный фронт-редактор контента для сайтов на migxpageconfigurator (mpc). Администратор правит содержимое страниц прямо на фронте (inline), не заходя в админку. Сохранение идёт в те же хранилища, что использует mpc: TV mpc_config, lexicon-файлы (переводы), нативные поля ресурса и произвольные TV.
Что умеет
Требования и зависимости
Как пользоваться
Мастер-выключатель всего пакета — системная настройка mpcve_active. Для работы также требуется включённая настройка mpc_edit_mode (пакет migxpageconfigurator) и перенарезка страниц — иначе в чанках нет data-mpc-* маркеров и редактор не подключится (предупреждение пишется в системный лог).
Системные настройки
mpcVisualEditor (mpcVE)
Вступление
Визуальный фронт-редактор контента для сайтов на migxpageconfigurator (mpc). Администратор правит содержимое страниц прямо на фронте (inline), не заходя в админку. Сохранение идёт в те же хранилища, что использует mpc: TV mpc_config, lexicon-файлы (переводы), нативные поля ресурса и произвольные TV.
Что умеет
- Inline-редактирование прямо на странице, по одному полю: правка сохраняется сразу, без перезагрузки и без перезаписи всей страницы.
- Поля любого типа: текст, textarea, форматированный текст (RTE), ссылки, изображения, picture/video/audio, а также TV-типы listbox, checkbox, tags, число, дата, файл.
- Списки (повторяющиеся блоки): добавление, удаление и перетаскивание строк, загрузка картинки в строку.
- Боковая панель секций: порядок (drag-drop), показ/скрытие секции, переключение «статическая секция».
- Панель скрытых полей: правка полей, которых нет в разметке страницы (настройки секции, стили, произвольные поля). Требует mpc 2.5.4+.
- Встроенный файловый менеджер (на modMediaSource): обзор, загрузка, создание папок, переименование и удаление, выбор существующего файла — прямо из редакторов изображения, медиа и файловых TV.
- История изменений: кто, когда и что менял, с пословным diff и откатом. Логируются правки и из редактора, и из админки.
- Блокировка ресурса: пока страницу правит один администратор, другой не откроет её на редактирование (TTL = idle-таймаут, настраивается).
- Мультиязычность: правка значений лексиконов с учётом текущего языка.
- Кнопки в тулбаре: очистить кэш сайта, открыть текущий ресурс в админке.
Требования и зависимости
- MODX Revolution 2.8.x
- PHP 7.4+
- migxpageconfigurator (mpc) 2.4.0+ — ЖЁСТКАЯ зависимость. mpcVE надстраивается над mpc и использует его address-space, edit-mode рендер и фасад. Для панели скрытых полей нужен mpc 2.5.4+.
- pdoTools (приходит вместе с mpc; нужен для рендера страниц).
Как пользоваться
- Дайте роли пользователей право (permission) mpcve_edit — оно проверяется при входе в режим правки. При сохранении дополнительно проверяется право save на конкретный ресурс (нельзя править чужое в обход).
- Откройте нужную страницу с query-параметром:
(имя параметра задаётся настройкой mpcve_edit_param, по умолчанию mpcedit).?mpcedit=1 - На странице появятся маркеры редактируемых блоков и тулбар. Кликаете блок — правите в открывшемся редакторе — изменение сохраняется сразу в хранилище mpc.
Мастер-выключатель всего пакета — системная настройка mpcve_active. Для работы также требуется включённая настройка mpc_edit_mode (пакет migxpageconfigurator) и перенарезка страниц — иначе в чанках нет data-mpc-* маркеров и редактор не подключится (предупреждение пишется в системный лог).
Системные настройки
- mpcve_active — включить/выключить пакет (по умолчанию: Да).
- mpcve_edit_param — query-параметр входа в режим правки (по умолчанию mpcedit →
).?mpcedit=1 - mpcve_permission — имя требуемого permission (по умолчанию mpcve_edit).
- mpcve_max_upload — лимит размера загружаемого изображения, байт (по умолчанию 10485760 = 10 МБ; 0 — без лимита).
- mpcve_lock_ttl — TTL блокировки ресурса = idle-таймаут, сек (по умолчанию 300).
- mpcve_allowed_attrs — белый список HTML-атрибутов для очистки содержимого редактируемого поля (по умолчанию class,href,src,alt,title). Обработчики
,on*
и опасныйjavascript:
режутся всегда, независимо от этой настройки.style
1.0.8-rc
Color-picker, контакты (иконки/класс), настройки, порядок секций, откат строк.
- Новый редактор color.js: пикер цвета + hex для полей config/TV (тип color/colorpicker). Значение хранится БЕЗ # (как админка MODX). FieldTypesHandler: color/colorpicker → редактор color.
- Контакты (contact.js/ContactSaveHandler): image-иконка через file manager (поле icon); правка class-режим (иконка-классом) с фильтром служебных классов mpcve-*.
- Настройки (info.js/SettingsListHandler): схлоп системной+контекстной записи в одну эффективную строку (target определяет, куда писать); виджет для image-xtype (file manager + превью); colorpicker без #; убран показ типа поля в модалке и панели.
- Секции (sidebar.js): порядок по position (-1 вверху, своя position приоритетнее типовой), клик по имени → скролл к секции на странице с подсветкой, поиск секции в DOM по MIGX_formname.
- Списки (rows.js): последняя строка очищается, а не удаляется (структура-образец сохраняется); inherit-диалог при добавлении строки в наследуемую секцию (api.js).
- История (RowOpHandler/LogHandler/Mpcve): откат row-операций (add/delete/move) — восстановление поля-списка к снимку до операции.
- CacheClearHandler: кнопка «Кэш» дополнительно чистит core/cache/system_settings/ (настройки/ClientConfig подхватываются без ручного сброса).
1.0.7-rc
Настройки: список из исходных шаблонов + тип виджета из БД; фикс дубля контакта.
- Панель «Настройки» теперь из settings/list: сервер сканирует ИСХОДНЫЕ шаблоны (mpc_path_to_src) на data-mpc-info → видны ВСЕ настройки, включая удалённые из вёрстки (data-mpc-remove). Тип/значение — из БД (settingMeta).
- Редактор настройки подбирает виджет по xtype из БД: combo-boolean → Да/Нет, colorpickerfield → color, code/textarea → textarea, иначе текст. Защищённые — с 🔒 без сохранения. И панель, и инлайн-клик берут тип из settings/list (кэш api.loadSettingsList/findSetting).
- Фикс дубля контакта при смене значения (см. mpc 2.5.34 — ckey identity). Файлы: SettingsListHandler.php (new), Connector.php, editors/info.js (rewrite), settings.js (rewrite), api.js, overlay.css.
1.0.6-rc
Редактирование контактов (data-mpc-cfield) из редактора.
- Правка полей контакта со страницы: value / caption / attributes. Клик по data-mpc-cfield → модалка-textarea с предупреждением «меняется на всех страницах». Запись через ContactSaveHandler (contact/save) → mpc Mpc::saveContact (грабер-логика: identity/лексиконы/мёрж в TV «Контакты»).
- ТРЕБУЕТСЯ data-mpc-key: контакты без ключа не помечаются редактируемыми (identity по md5(value) нестабилен) — подсказка «добавьте ключ». Под правом mpcve_edit_global (как настройки).
- Аудит в истории (action=contact, revertable=0). Файлы: ContactSaveHandler.php, Connector.php, editors/contact.js, mark.js, app.js, editors/index.js.
1.0.5-rc
Редактирование служебных настроек (data-mpc-info) из редактора.
- Правка глобальных настроек сайта (системные/контекстные/ClientConfig) прямо со страницы: инлайн по видимому data-mpc-info + панель «⚙ Настройки» в тулбаре со списком ВСЕХ data-mpc-info страницы (включая — favicon/метрики, по которым нельзя кликнуть). Редактор — модалка-textarea с предупреждением «меняется на всех страницах».
- Новое право
mpcve_edit_global(PermissionChecker::canEditGlobal, прокинуто во фронт как editGlobal): без него настройки не помечаются редактируемыми и кнопка/панель скрыты. Регистрируется резолвером в политике Administrator. - Запись через InfoSaveHandler (action info/save) → mpc InformationUpdater::saveSetting (blacklist защищённых). Аудит в истории (revertable=0). Файлы: InfoSaveHandler.php, PermissionChecker.php, Mpcve.php, Connector.php, editors/info.js, settings.js, mark.js, app.js, constants.js, overlay.css, resolvers/1permissions.php.
1.0.4-rc
Санитайз вставки, откат медиа-правок, работа с секциями типа в сайдбаре.
- Санитайз при вставке ВЕЗДЕ: инлайн-текст (text.js) и textarea вставляют только text/plain; RTE (rte.js) чистит буфер через sanitizeHtml (разрешённые теги/атрибуты остаются, style/on*/чужая разметка срезаются). Иначе вставка из IDE тащила стили в поле и в лексикон. + saveField прогоняет keepHtml-значение через sanitizeHtml перед отправкой.
- Медиа-редакторы (image/picture/media) теперь шлют
oldв field/save → правки картинок/видео стали откатываемыми с диффом в истории (раньше — только аудит). file.js уже слал old. - Сайдбар «Секции»: объединённый список — свои секции + наследуемые из ТИПА под 🔒 (клик копирует секцию в текущий ресурс). Сверху массовые операции (только для не-типов): «Дополнить из типа» (merge), «Перезаписать из типа» (overwrite, с подтверждением), «Очистить» (с подтверждением). На странице-типе — только свои секции. Файлы: sidebar.js, overlay.css, Mpcve.php (copySectionFromType/copySectionsFromType/isTypeResource), ConfigGetHandler.php (отдаёт type+isType), SectionOpHandler.php (ops copy_one/from_type/clear).
1.0.3-rc
Фикс: смена картинки в простом image-TV ломала шаблон.
- editors/image.js (isRecordImage): data-mpc-tv трактуется как путь-строка, а не migx-запись. Раньше редактор слал в простой image-TV массив [{MIGX_id,src,alt,title,width,height}], тот попадал в src="{$resource.tvs.}" и валил Fenom при запекании parsed/ («Unexpected token ':' near '{"MIGX_id":'»). migx-TV в редакторе пока не поддержаны (M29), так что любой data-mpc-tv — путь; запись-массив остаётся только для config-полей (data-mpc-field). В пару к серверному барьеру FieldWriter::writeTv (mpc 2.5.30).
- docs/readme.txt переоформлен в едином стиле с mpc (allowed-tags разметка).
1.0.2-rc
Документация и порядок в системных настройках.
- readme.txt переписан: что умеет / требования и зависимости / как пользоваться / системные настройки.
- TODO.md (dev-роадмап) убран из файлов пакета.
- Все 6 системных настроек получили название и описание (ru/en) — раньше показывались сырыми ключами без описаний. Сгруппированы по областям: «Основные» (mpcve_general) и «Редактор» (mpcve_editor), с русскими заголовками.
1.0.0-alpha (в разработке)
Визуальный фронт-редактор контента для проектов на migxpageconfigurator (mpc). Зависит от mpc 2.4.0+ (для панели скрытых полей — 2.5.4+). Ведётся поэтапно; в прод — после перехода на -rc.
- [M26 шаг 3] Модалка истории изменений (вместо панели).
changelog.jsпереписан: модалка с ТАБЛИЦЕЙ (время/юзер/уровень/секция·поле/было→стало/откат). Фильтры: секция → зависимое поле (опции из самих записей), источник (редактор/админка поsource), сортировка по дате (↑/↓). Инлайн-ДИФФ старое↔новое по словам (LCS,красным /зелёным; фолбэк «старое→новое» при >300 токенов). Клик по диффу — развернуть полное содержимое (CSS max-height). Откат (↩) — как был (log/revert + reload). Источник из админки помечен ⚙. БэкLogHandler::listтеперь отдаётsource/level(из address). CSS.mpcve-clogm*. Файлы:changelog.js(переписан),LogHandler.php,overlay.css. M26 (полный лог редактор+админка) завершён. - [M26 шаг 2] Аудит правок из АДМИНКИ в лог изменений (закрыта половинчатость).
Handlers/AdminAudit:OnBeforeDocFormSaveснимает СТАРЫЕ значения прямым SQL из БД (на OnBefore нативные поля уже = новые из формы, TV ещё старые → object-state ненадёжен);OnDocFormSaveв начале (ДО re-cut handleFile, иначе ре-граб перетёр бы новое) диффит old↔new и пишет в{prefix}mpcve_changelog. Трекаем: все скаляр-поляmpc_config(config=mpc-контент) + rfield/tv из манифестаmpc_tracked_fields. УРОВЕНЬ (resource/type/global по сохраняемому ресурсу: id==sbp→global, parent==sbp→type, иначе resource) иsource=adminкладём вaddressJSON — без ALTER таблицы; откат уровне-aware (LogHandler→writeField). Списки/записи — лог коротко (revertable=0). СобытиеOnBeforeDocFormSaveЗАРЕГИСТРИРОВАНО на стенде (modPluginEvent, plugin id=1) — ДОБАВИТЬ в build-декларацию плагина для пакета. Проверено симуляцией (longtitle + hero.content → записи с level=type, source=admin). Файлы:Handlers/AdminAudit.php(new),Plugins/OnBeforeDocFormSave.php(new),Plugins/OnDocFormSave.php. Осталось шаг 3 — модалка истории (фильтры из манифеста, source-фильтр, инлайн-дифф). - [M26 шаг 1] Манифест трекаемых полей (для полного лога изменений: редактор + админка). На нарезке
SectionProcessor::rebuildTrackedFieldsсобирает из data-mpc-* маркеров шаблона набор трекаемых полей (field — верхний уровень секции, kind=list для списков; rfield/tv — по всему html) и пишет в таблицу{prefix}mpc_tracked_fields(helperHandlers/TrackedFields, raw PDO, авто-создание). Глобальный союз со всех шаблонов, строки тегированыtemplate_id→ на нарезке replace-by-template (без стейла). Уровень (resource/type/global) в манифесте НЕ хранится — определяется при записи лога по сохраняемому ресурсу (для отката). Назначение: membership-фильтр админ-лога (особенно rfield/tv, которых нет в mpc_config) + источник выпадаек «секция→поля» модалки истории. Шаги 2-3 (админ-лог diff+level в OnDocFormSave, модалка) — далее. Файлы:Handlers/TrackedFields.php(new),Grabber/SectionProcessor.php. - [mpc fix] Умный мерж при перенарезке путал секции с одинаковым data-mpc-section. Симптом: две секции
data-mpc-section="features"(оригиналdata-mpc-lexicon="features"+ копияdata-mpc-lexicon="features_copy"data-mpc-copy) — после СОХРАНЕНИЯ ресурса (OnDocFormSave→ре-граб с умным мержем) оригинал начинал показывать заголовок копии. Причина:SectionProcessor::indexConfigBySectionи lookup существующей секции для мержа ключевались поMIGX_formname(="features" у обеих) → коллизия → обе получали$existingодной секции (копии, последней в индексе), её значения затирали свежий граб оригинала. На ПЕРВОЙ нарезке existing пуст → мержа нет → было верно (отсюда «после нарезки ок, после сохранения нет»). Фикс: ключ мержаsectionMergeKey=MIGX_formname|lexicon_prefix(data-mpc-lexicon различает оригинал/копию; префикс всегда непуст — фоллбэк sectionName, однотипные секции матчатся как прежде). Файл:Grabber/SectionProcessor.php. ВНИМАНИЕ: уже испорченный конфиг лечится полной перенарезкой (mgr_tpl, updContent — шаблон перезаписывает), т.к. умный мерж сохраняет существующее значение как «правку админа». - [M25.2] Инвалидация parsed при правке значения (фикс «значение не меняется после правки»). Причина: для нестатичных секций значения ЗАПЕКАЮТСЯ в parsed/.tpl на eager-пассе ($listbox на финальном per-request пассе уже нет — ленивый резолв возможен только в статичных секциях через getStaticSection), а parsed регенерится ТОЛЬКО при отсутствии файла. Теперь parsed правленого ресурса сносится при сохранении → пересборка с новым значением при следующем заходе. Редактор:
FieldWriter::afterSave→invalidateParsed(файл ресурса; а если ресурс-ТИП/донор parent=staticBlocksPage или level=global → весь parsed, т.к. влияет на наследующих). Админка:Plugins/OnDocFormSave— то же (донор → clearCache, иначе deleteParsedConfigFile). Файлы:FieldWriter.php,Plugins/OnDocFormSave.php. - [M27.1] Скрытые поля секции — ЯВНЫЙ список исключений (со слов пользователя) вместо хардкод-
STRUCTURAL.isSectionExcluded=S.settingsFields(таб «Настройки секции» из mpc_base) +css_file_path(путь к файлу стилей) + migx-служебка (MIGX_id/MIGX_formname/limit) + стилевые (показаны отдельной веткой с каскад-уровнем). ВСЁ прочее из конфига, чего нет в DOM (data-mpc-field), → в скрытые, включая кастомные поля (раньше хардкод-STRUCTURAL мог не учесть). STRUCTURAL оставлен только для служебных под-полей СТРОК списков (itemHidden). Файл:panels.js. - [M27] Стили секции: поле
props(«Дополнительные свойства», textarea) добавлено в редактируемую стилевую ветку панели скрытых полей (SECTION_STYLE_FIELDS+= props, и вSTRUCTURALчтобы не дублировалось в контентной ветке).css_file_pathостаётся неправимым (путь к файлу). inline_styles/class_names были раньше. Файл:constants.js. - [M25.3] listbox-multiple доведён. Значение хранится СТРОКОЙ ключей через "||" (формат MODX/migx — admin-сетка корректно round-trip'ит). ВАЖНО (фикс багов): сначала пробовал хранить массивом — admin-migx-виджет КОРНЕВОГО поля стрингифил массив-объект в "[\"tg\",\"ph\"]" →
{foreach}по строке ломался (корневое не сохранялось/не рендерилось; вложенное в JSON строки списка переживало). Решение — строка "||" +| splitв Fenom. Капшены опций лексиконизируются и для multiple (writeListboxOptions($multiple=true)). Плейсхолдер(PlaceholderProcessor): лексиконы ВКЛ → `{set $mpcItems = $field | split: '||'}{foreach $mpcItems as $item}##('{prefix}_{field}_{$item}')|lexicon}{if !$item@last}, {/if}{/foreach}` (капшены через запятую); ВЫКЛ → `{$field | split:'||' | join: ','}`. ContentParser нормализует "tg,ph"/"tg||ph" → "tg||ph". Редактор `listbox.js`: multiple читает DOM по запятой, сохраняет "||"-строку (raw=1). Проверено: root и nested рендерят «Telegram, Phone», editor round-trip (write "dc||em" → «Discord, Email»). Файлы: `Cutter/PlaceholderProcessor.php`, `Grabber/{LexiconManager,FieldValueExtractor,ContentParser}.php`, `FieldWriter.php`, `editors/listbox.js`. Ручной foreach/join в шаблоне не нужен — каттер генерит в. - [M25.1] Лексиконизация listbox (одиночного). Схема: капшены опций уезжают в лексикон ключами
{sectionPrefix}_{field}_{optionKey}(пустой key →..._), значение поля = СЫРОЙ ключ опции, на рендере резолв##'{prefix}_{field}_{$value}' | lexicon}. Условия (грабер и каттер считают одинаково, оба видят элемент+значение): список keyed (Caption==key), текущее значение ∈ ключей, shouldLexiconize('text',…), НЕ multiple. Иначе — сырое{$value}(без лексикона). Реализация:LexiconManager::{getSectionPrefix,parseListboxOptions,writeListboxOptions};FieldValueExtractor::getValue(listbox-ветка: пишет капшены, хранит сырой ключ);PlaceholderProcessor::{setDefaultPlaceholder,listboxPlaceholder}(плейсхолдер с ключом-конкатом; deferVar → Fenom~). Save:FieldWriter::writeConfigFieldуважаетaddress.raw=1(пропускает лексиконный блок) — редакторlistbox.jsшлёт raw=1 (значение=ключ, не лексиконить повторно). Фронтlistbox.js: текущий выбор по капшену И по ключу (после лексикона DOM показывает капшен), на сохранении обновляет DOM тем же видом. Multiple — без лексикона (значение "k1||k2" несовместимо с конкат-ключом). Действует после перенарезки. Файлы:Grabber/{LexiconManager,FieldValueExtractor}.php,Cutter/PlaceholderProcessor.php,FieldWriter.php,editors/listbox.js. - [M25 ФАЗА 2 — фронт mpcVE] Редактор listbox + лимит строк. (1) Новый редактор
editors/listbox.js(select / multi-select): опции из data-mpc-values формата "Caption==key||..." парсятся на фронте; "@SELECT ..." (динамика migx) на фронте не резолвится в v1 → ручной ввод значения с подсказкой. Сохранение field/save (значение=ключ; multiple → ключи через "||"). Роутинг типа:address.ftypeToEditor(listbox/listbox-multiple),FieldTypesHandler.editorType(по inputTVtype), реестрeditors/index.js, подсказкиconstants.TYPE_HINT, стиль.mpcve-lb__sel. (2)rows.js: data-mpc-max с контейнера списка → блокировка «+ Добавить» при достижении лимита (кнопка disabled + причина в title, и guard на клик). Файлы:editors/{listbox(new),index,rows}.js,address.js,constants.js,overlay.css,mpcvisualeditor/.../FieldTypesHandler.php. ОГРАНИЧЕНИЯ v1: @SELECT-опции — ручной ввод (резолвер позже); листбокс-значение не лексиконизируем (enum-ключ). Действует после перенарезки (чтобы появился listbox-тип в конфиге) + Ctrl+F5. - [M25 ФАЗА 1 — каттер] Атрибуты data-mpc-values и data-mpc-max в нарезке (
SectionProcessor). (1) data-mpc-values — опции listbox: значение вставляется КАК ЕСТЬ вinputOptionValuesfield-определения (migx сам разбирает "Caption==key||..." и "@SELECT ..."), тип поля →listbox(если автор не задалlistbox/listbox-multipleчерез data-mpc-ftype). Новые синтез-типыlistbox/listbox-multipleвsynthesizeScalar. (2) data-mpc-max на контейнере списка →extended.maxRecordsавто-конфига строки (buildListConfig→saveListConfig), migx ограничивает число записей. Правки:fieldAttrs(+values,+max),synthesizeScalar,makeFieldDef,buildListConfig,saveListConfig. Применяется ПОСЛЕ перенарезки шаблона. ФАЗА 2 (фронт mpcVE: редактор-выпадайка + учёт max в rows.js) — отдельно. Файлы:migxpageconfigurator/.../Grabber/SectionProcessor.php. - [M24.2] Диалог наследуемой секции — 2 варианта (со слов пользователя): «Скопировать в эту страницу» (inherit=copy) ИЛИ «Открыть страницу-источник и править там» (редирект на ресурс-тип, где секция реально лежит — правка там обычным resource-путём). Убран вариант «Править тип» (запись типа из текущей страницы). Бэк добавляет в ответ inherit_choice
typeResourceId+typeUrl(makeUrl(..,'full')); фронт на выборgotoделаетwindow.locationна источник (edit-mode держится cookie). Backend-режим inherit=type оставлен в FieldWriter (не используется диалогом). Фикс: на выбореgotoвозвращаем «вечный» промис (неres), иначе редактор мигал тостом 'section inherited...' за миг до навигации. Файлы:FieldWriter.php,api.js. - [M24.1] Фикс: двойной/повторный клик по инлайн-полю внутри перезагружал страницу.
bindClicksпри уже открытом инлайн-редакторе (contenteditable=true) делал раннийreturnбезpreventDefault→ второй клик по тексту внутри ссылки-обёртки уходил в навигацию по href. Теперь в этом случае гасим переход, если клик внутри` (каретка ставится на mousedown — preventDefault на click её не ломает). Файлы:app.js`. - [M24] Наследование уровней при записи поля (фикс «empty mpc_config for level resource»). Причина: ресурс с пустым
mpc_config(напр. главная res 1) наследует секции от ТИПА (Render мёржит type+resource; лексиконы — каскад global→type→resource вgetLexiconFilenames), аFieldWriter::writeConfigFieldлез прямо в пустой конфиг ресурса. Теперь: если секции нет в конфиге ресурса (наследуется) → по выбору юзера (address.inherit):type— пишем в конфиг/лексикон ТИПА (resolveLevelResource('type'), глобально для шаблона);copy—seedSectionIntoResourceкопирует объект секции из type-конфига вmpc_configресурса + лексикон-ключи секции (поlexicon_prefix) из файла типа в файл ресурса (тот же ключ → ресурсный перекрывает типовой при мердже, перенарезка не нужна), дальше обычная resource-запись; без выбора — бэк отдаёт{code:'inherit_choice', section}. Фронт (api.js, централизованно для всех редакторов): ловитinherit_choice→choiceDialog(Скопировать в страницу / Править тип / Отмена) → повтор сinherit. Статичная секция (level=global) — без диалога, тост-предупреждение «изменение глобально». Новое:FieldWriter.{configHasSection,seedSectionIntoResource},dom.choiceDialog,apirawPost+handleFieldSave, CSS.mpcve-confirm__actions. ОГРАНИЧЕНИЕ: покрытfield/save(скаляр/медиа-поля); row/op (добавление строк списка) на наследуемой секции пока не сидирует — следующий шаг. Файлы:migxpageconfigurator/.../FieldWriter.php,mpcvisualeditorapi.js/dom.js/overlay.css. - [M23.3] Source-режим распространён на textarea + переключатель в richtext. (1)
textarea.js: поле с разметкой (el.querySelector('*')) правится как СЫРОЙ HTML (показываемel.innerHTML, на сохраненииel.innerHTML = value); плоский текст — прежний multiline plain. Симметрично инлайн text.js (M23.2). (2)richtext.js: кнопка « Код / Визуально» в модалке — переключение визуальный RTE ↔ правка сырого HTML в textarea (теги видны/правятся руками). Содержимое синхронизируется при переключении (черезsanitizeHtmlпо allowedTags), сохраняется HTML активного режима; RTE пересоздаётся на том же host. CSS:.mpcve-modal__spacer(распорка actions),.mpcve-rte__source(моноширинный). Файлы:editors/textarea.js,editors/richtext.js,overlay.css. - [M23.2] Инлайн-правка поля с разметкой = правка ИСХОДНОГО HTML. По просьбе пользователя: при правке поля с тегами (
,и т.п.) редактор показывает сырой HTML как редактируемый текст (Подобрать подушку), чтобы теги можно было удалить/заменить (раньше contenteditable показывал отрендеренный жирный — самих тегов не видно).openTextEditor:sourceMode = el.querySelector('*')→ на открытииel.textContent = innerHTML(теги видны), на закрытииel.innerHTML = el.innerText(сырой HTML → реальная разметка) +saveField(читает innerHTML = разметку). Плоский текст — прежний WYSIWYG. Esc возвращает исходный рендер. Заменяет подход M23.1 (там показывали рендер с кареткой в конец — теги всё равно не были видны). Файлы:editors/text.js. - [M23.1] Фикс: при инлайн-правке поля с разметкой форматирование исчезало «на глазах».
openTextEditorпри открытии выделял ВСЁ содержимое (range.selectNodeContents→ select-all); для поля с//… первый же ввод заменял выделение плоским текстом → визуально «осталось только текст». Теперь: поле с разметкой (el.querySelector('*')) — каретку в КОНЕЦ (range.collapse(false)), содержимое целиком сохраняется и правится по месту; плоский текст — прежнее select-all (удобно перепечатать). Вместе с M23 (сохранение innerHTML) инлайн-правка полностью сохраняет вложенную разметку. Файлы:editors/text.js. - [M23] Фикс: инлайн-редактор текста срезал HTML-разметку поля. Поле вида
…грабер сохраняет С разметкой (FieldValueExtractor::getValue: есть дочерние теги → HTML детей), ноtext.jsпри сохранении бралel.innerTextдля всего, кромеrichtext→ после первой правки` пропадал, поле становилось плоским текстом. ТеперьsaveFieldотдаётinnerHTML, если в поле есть разметка (el.querySelector('*')) или типrichtext; чистый текст по-прежнемуinnerText(без лишнего экранирования &/ TTL — авто-выход (release + reload в view). Снятие: явный «Завершить» (releaseOnExit) и закрытие вкладки/уход (pagehide→navigator.sendBeaconlock/release). Сеть упала на acquire → правку не блокируем (fail-open). Settingmpcve_lock_ttl(_build/mpcvisualeditor/elements/settings.php). Файлы:Handlers/LockHandler.php(new),Connector.php,lock.js(new),app.js,overlay.css(.mpcve-lockbanner`). - [M20] Тулбар: безопасные кнопки «🧹 Кэш» (полная очистка кэша MODX — экшен
cache/clear→CacheClearHandler→cacheManager->refresh()) и «⚙ Админка» (открыть текущий ресурс в админке:managerUrl?a=resource/update&id=` в новой вкладке; `managerUrlдобавлен в client-config из настройкиmanager_url). Перенарезку-из-фронта НЕ делаем (деструктивна — отдельный заход с confirm). Файлы:Mpcve.php(+managerUrl),Handlers/CacheClearHandler.php(new),Connector.php(+экшен),app.js(кнопки тулбара). - [M19.1] Фикс сайдбара: структурные операции над секциями через отдельный экшен
section/op(RAW-запись массива конфига), а НЕfield/save. Причина багов:field/saveдляposition/hide_section/is_staticЛЕКСИКОНИЗИРОВАЛ значение в ключ (если content-type поля переводимый) — ключ всегда truthy → видимость переключалась в одну сторону, порядок/статичность ломались. Плюс миграцииis_staticне было вообще. НовыйSectionOpHandler+Mpcve::saveConfig/FieldWriter::saveConfig(пишут весь массив секций сырьём, без лексикона, + сброс кэша):move(новый порядок имён → переставить массив + position 1..N),visibility(hide_section),static(is_static + МИГРАЦИЯ: при включении копия секции кладётся в конфиг staticBlocksPage — рендер берёт контент оттуда; при выключении убирается). Reorder — один запрос (был N). Фронтsidebar.jsзовётsection/op. Файлы:FieldWriter.php(+saveConfig),Mpcve.php,Handlers/SectionOpHandler.php(new),Connector.php,sidebar.js. Тесты mpc 290/290. - [M19] Сайдбар управления секциями (порядок / видимость / статичность). Кнопка «☰ Секции» в тулбаре (в режиме правки) → фиксированный сайдбар справа со списком секций ресурса (из
configData.resource, сорт поposition). Для каждой: drag-хваталка «⋮⋮» (переупорядочивание), 👁 видимость (hide_section), 📌 статичность (is_static). Всё пишетсяfield/saveна уровнеresource(стаб секции c position/is_static/hide_section живёт в ресурс-конфиге; рендерRender::parseConfigмёржит type+resource и сортируетuasortпоposition). Drag → переназначаетposition1..N и пишет изменённые. Эффект (скрытие/порядок/статичность меняют РЕНДЕР) — после «Обновить». Файлы:sidebar.js(new),app.js(кнопка тулбара),overlay.css. v1-ограничения: список — секции ресурс-конфига (секции только из type/donor-конфига не показываются); сменаis_staticфлипает флаг черезfield/save— миграция контента resource↔static зависит от существующего механизма mpc (проверить на данных). - [M18] RTE-переработка: тулбар из
mpc_allowed_tags, события, картинки, pluggable. (1) Тулбар строится из системной настройкиmpc_allowed_tags(прокинута во фронтMpcve::getClientConfig→S.cfg.allowedTags) — кнопка показывается только если её тег разрешён (бессмысленно предлагать тег, который вырежетstrip_tagsна записи лексикона). Маппинг тег→кнопка: b/i/u/s→exec, small/mark/span→обёртка выделения (нет в execCommand), p/h1..h6/blockquote→formatBlock, ul/ol→списки, a→ссылка, img→картинка; «очистить формат» — всегда. (2) Картинки — через наш загрузчик (api image/upload/uploadMedia), кнопка только еслиimgразрешён. После загрузки — диалог атрибутов (.mpcve-imgdlg: превью,alt,title, замена файла) → вставка` с атрибутами черезinsertHTML(голыйinsertImageне давал задать атрибуты — фикс по тесту). Клик по уже вставленной картинке открывает тот же диалог для правки её атрибутов. (3) Событияmpcve:rte:nodeadded/noderemoved/nodechanged(MutationObserverна области, всплывают,detail.node[,attribute]) — проект слушает и навешивает классы/обработчики на вставленные теги. (4) **Pluggable**:editors/rte.js— дефолтный провайдер + реестр;window.MpcVE.setRte(provider)подменяет редактор (provider.create(container,{value,allowedTags,upload})→{getHTML,focus,destroy}). (5) КлиентскийsanitizeHtml(html, allowedTags)на save — зеркалитstrip_tagsбэка (разрешённые теги с атрибутами остаются, прочие разворачиваются), применяется в richtext-модалке и панели независимо от провайдера. Fallback: еслиallowedTagsне пришёл (старая кэш-страница) — дефолтный набор тегов (пустой[]= настройка реально пустая = ничего не разрешено). ⚠️ чтобы появились кнопки ссылки/картинки — добавьa,imgвmpc_allowed_tags. Файлы:Mpcve.php,editors/rte.js(переписан),editors/richtext.js,panels.js,mpcve.js,overlay.css`. - [M17] Списки: drag-drop переупорядочивание строк (вместо ↑/↓). У каждой строки grip-хваталка «⠿», строка
draggable; перетаскивание → серверныйmove(from→to)(array_spliceвConfigFieldWriter::moveRow— значения/лексикон-ключи едут со строкой) + перестановка узла страницы (splice-семантика: to>from → после ref, иначе перед ref) + перерисовка панели. Кнопки ↑/↓ убраны, ✕/📷 остались.editors/rows.js(wireDrag,dragFrom),overlay.css(.mpcve-rows__grip/--drag/--over). Touch-DnD (мобильные) — позже при необходимости. - [M16.5] Скрытые поля: picture/video/audio-ЗАПИСИ редактируются в панели (закрыт хвост — раньше пропускались). Редакторы
picture/mediaполучили value-based режим:openPictureEditor(el, override)/openMediaEditor(el, override)— приoverride={addr[, isVideo]}адрес/запись берутся из конфига (fieldConfigRecord),elможет быть null (превью пустые, DOM нет); на странице — как было. Панель (panels.js):recordKindопределяет тип записи по форме (вложенныйimg/sources[].srcset→ picture;sources[].src+ poster/width → video, иначе audio); для таких полей в строке — кнопка «✎ Редактировать», открывает полный редактор value-based. Панель — сама.mpcve-modal, поэтому сперва закрывается (иначе guard редактора блокирует). v1: только секционные записи (item-level nested —fieldConfigRecordне достаёт). Файлы:editors/picture.js,editors/media.js,panels.js. - [M16.4] Фикс: тип редактора для TV/rfield брался по совпадению ИМЕНИ с config-полем (TV
subtitleловил тип одноимённого config-поля → открывался textarea вместо простого текста). Причина:editorTypeForбралS.typesMap[name](карта config-полей mpc_base) для ЛЮБОГО адреса. Теперь карта по типу адреса:tv→ отдельная карта типов TV (FieldTypesHandlerотдаётtvs= имя TV → тип поmodTemplateVar.type);rfield→ стандартные типы ресурс-полей MODX (content→richtext,introtext/description→textarea, прочее→text);field→ прежняяtypesMap.data-mpc-ftypeна маркере по-прежнему переопределяет. Файлы:FieldTypesHandler.php(+tvs),state.js(tvTypes),app.js,address.js(RFIELD_TYPES+ выбор карты поaddr.type). - [M16.2] Скрытые поля: одиночные img-ЗАПИСИ теперь редактируются в панели (раньше пропускались —
parseRecordотсекал любые записи).isImgRecord(естьsrc, нетsources/img— так отличаем от picture/video/audio-записей со sources/вложенным img); такой дескриптор идёт типомimageс полемrecord. Контрол image (превью+загрузка) теперь value-based для записей:getValueсобирает запись целиком (структура сохраняется), меняет толькоsrcпри загрузке нового файла — иначе оставляет исходныйsrc(лексикон-ключ → бэк не трогает). Адрес/запись через тот жеfield/save, что и on-page img-редактор. picture/video/audio-записи в панели пока пропускаются (реже, нужен value-based порт тех редакторов). Файл:panels.js. - [M16.1] Скрытые поля правятся ПО ТИПУ (как на странице). RTE-тулбар вынесен в общий
editors/rte.js(rteToolbarHtml/wireRteToolbar) — используют и модальный richtext-редактор, и панель скрытых полей. В панели (panels.js) richtext теперь не голый contenteditable, а RTE с форматированием (тулбар над областью,.mpcve-hpanel__rtebox); textarea (тип отдаётсяFieldTypesHandlerотдельно с M16) —`; text — input; image — загрузка. Файлы:editors/rte.js(new),editors/richtext.js(использует общий),panels.js,overlay.css`. - [M16] Текст: инлайн только для простого text; textarea и richtext — в МОДАЛКЕ. Раньше и text, и richtext правились инлайново (contenteditable), многострочный textarea тоже шёл как text. Теперь: (1)
text— инлайн contenteditable (как было, простые однострочные); (2)textarea— модалка с` (значение plain text, обновляемel.textContentбез перезагрузки) — новыйeditors/textarea.js; (3)richtext— модалка с настоящим RTE: тулбар B/I/U/S, H2/H3/¶, маркир./нумер. списки, ссылка (с возвратом выделения после prompt), убрать ссылку, очистить формат — на нативномdocument.execCommand(без сборки/зависимостей); значение = innerHTML, пишетсяfield/save(лексикон-round-trip как у инлайна) — новыйeditors/richtext.js. Детект типа:FieldTypesHandler::editorTypeтеперь отдаётtextareaотдельно отtext;ftypeToEditor(address.js) —textarea/richtext. Реестрeditors/index.js:richtextбольше НЕ роутится в инлайн-текст. Файлы:editors/{textarea,richtext}.js(new),editors/index.js,address.js,constants.js(TYPE_HINT),overlay.css(.mpcve-ta*/.mpcve-rte*),FieldTypesHandler.php`. RTE на execCommand — v1; при необходимости позже заменить на полноценный редактор. - [M15.3] Аудио: кнопка-аффорданс «✎» для открытия редактора. M15.2 (force controls) сделал
видимым, но клик по нему всё равно не открывал редактор:целиком занят НАТИВНЫМ контрол-баром — UA обрабатывает клик внутри (play/seek) и НЕ шлётclickна host-элемент, поэтомуbindClicksне срабатывал (у video есть кликабельный кадр над баром — потому video работал). ТеперьattachAudioBadges(app.js) оборачивает каждыйaudio.mpcve-editableв.mpcve-audio-wrapи вешает кнопку.mpcve-media-badge(✎) → клик открываетeditors.media.removeAudioBadgesразворачивает при выходе. CSS —overlay.css. force-controls (M15.2) оставлен как гарантия видимости (если секцию перережут сcontrols=0→ условный рендер не выведет атрибут). Обёрткаinline-blockсо shrink-to-fit схлопывала аудио с темовымwidth:100%до мин-ширины (бар превращался в огрызок) — JS замеряет ширину аудио ДО оборачивания и задаёт её обёртке (> audio { width:100% }), вид сохраняется. - [M15.2] Фиксы по тестированию video/audio: (1)
` безcontrolsимеет нулевую видимую область (0×0) → по нему нельзя кликнуть, чтобы открыть редактор (видео всегда видно — кадр/постер). В режиме правкиmarkElвременно включает нативныеcontrolsна audio (data-mpcve-controls-флаг),unmarkEditableснимает их при выходе. (2) Сайтовыйexpand.js(getImgData) падалCannot read properties of undefined (removeAttribute), если контент по data-lazy/src — не SVG (битый путь/растр/медиа): добавлен null-guardif (!svg) return. Файлы:mark.js,app.js,migxpageconfigurator/js/web/expand.js`. - [M15.1] Булевы атрибуты video/audio стали редактируемыми (чекбоксы controls/autoplay/loop/muted) — после того как каттер начал рендерить их УСЛОВНО (mpc 2.5.22-rc:
renderBoolAttrs→{if $rec.controls}controls{/if},0реально выключает). Редактор шлёт 1/0;preload(mpc теперь грабит строкой) правится селектом. Работает, если атрибут был в разметке на нарезке. Фронт:editors/media.js(вернули чекбоксы + boolOf/attrInit),overlay.css(убрал заглушку-note). - [M15] Редактор VIDEO/AUDIO (одиночные). Клик по
/(типmedia) → модалка: основной файл (загрузка), постер (только video, картинка),preload(селект), булевы атрибуты (controls/autoplay/loop/muted — см. M15.1) и массив источников` ({type, media, src}) с загрузкой/добавлением/удалением. Источник правды — конфиг-запись (fieldConfigRecord): неизменённыеsrc/poster/sources[].srcшлём КЛЮЧАМИ лексикона (бэкmergeMediaRecordих не трогает, превью — из DOM), изменённые/новые → загруженный URL. Запись переносится целиком (Object.assignпо ключам конфига) с переопределением правленых полей — литеральные ключи (title/width/height/controls/…) НЕ теряются (mergeMediaRecord лексиконит только src/poster/sources[].src; прочее — литерал). Бэк:ImageUploadHandlerобобщён подkind(image|video|audio, по умолчанию image — обратная совместимость): белые списки расширений video/audio, mime-проверка через finfo (ogg/webm-контейнеры — мягко), подпапкиvideos//audios/; новыйprocessUpload-параметр$defaultBase. Лексиконmpcve_err_upload_extобобщён («Недопустимый тип файла»). Фронт:editors/media.js(new),api.js(uploadMedia(file,kind)),editors/index.js(+media),constants.js(TYPE_HINT),overlay.css(.mpcve-media__*`). Загрузка нескольких источников + замена основного файла — рабочие. picture (M12) — без изменений. - [M14] Ссылки: блокировка навигации в edit-mode + редактор адреса (href). (1) В режиме правки клик по любому
(внутренние/внешние, маркированные и нет) больше НЕ переходит по ссылке — `bindClicks` (app.js) гасит навигацию для немаркированных ссылок (`e.target.closest('a')` → preventDefault), а маркированные открывают редактор. (2) Маркер (data-mpc-field/rfield/tv) НА самом теге/→ каттер кладёт значение поля в `href` (Cutter.php), значит значение поля = АДРЕС ссылки: `editorTypeFor` (address.js) для тега a/link возвращает тип `link` (детерминированно, приоритет над ftype), новый редактор `editors/link.js` правит href через `field/save` (бэк не менялся — пишет URL так же, как при нарезке, лексикон-aware round-trip). Текст ссылки правится отдельным маркером на элементе ВНУТРИ(обычный текст-редактор) — без изменений. Осталось по п.2 TODO: атрибут-поля кнопок (id целевой модалки и пр.) — нуженdata-mpc-attr-маркер на нарезке (сейчас зарезервирован в Cutter, не используется), вынесено отдельным шагом. - [M11.6.1] Фиксы вложенных списков (по следам тестирования): (1) клон новой строки снимает с картинок lazyload/expand-триггеры (
data-lazy/data-svg, хелперblankImg) — иначеexpand.jsсайта падал на пустой картинке клона (getImgData: fetch несуществующего пути → нет` →removeAttributeof undefined). (2) Лексиконизация полей вложенной строки нового элемента — см. mpc 2.5.20-rc (fieldUsesLexiconKeysрасширен): раньше «значения сохраняются, но после reload пустые» (литерал в конфиге + чанк{$f|lexicon}`). - [M11.6] Вложенные списки + JS-рендер добавления строки (без автоперезагрузки). (1) ВЛОЖЕННЫЕ списки теперь редактируются построчно:
openRowsEditorбольше их не отклоняет;listAddressдля контейнераdata-mpc-field-Nстроитpath(спуск к родительской строке черезbuildRowPathпо уровню N),parentField= имя самого списка → бэкmutateRowsпо path (mpc 2.5.19). Детект списка обобщён:listFieldAttr/itemAttrForLevel,isListEl/listRowsработают по уровню вложенности (строки уровня N —data-mpc-item-N), фильтрclosestне захватывает строки более глубоких списков. (2) Операции отражаются в DOM средствами JS, БЕЗ автоперезагрузки (раньше каждая операция дёргалаwindow.location.reload()):delete— убираем узел;move(↑/↓) — переставляем узлы;add— клон первой строки как ШАБЛОН с очисткой значений (cloneBlankRow: скаляры → пусто+CSS-плейсхолдер, медиа → прозрачный 1×1, контейнеры вложенных списков НЕ чистим — структура строк сохраняется, бэк сидирует тем же deep-clear) +markFieldsWithin(поля клона сразу редактируемы). Так в новом элементе дочерний список заполняем сразу. Перезагрузка — вручную (кнопка «Обновить» в панели), когда нужен точный рендер шаблона (лексикон/условные блоки). Разметка поля вынесена вmark.js(markEl/markFieldsWithin) — общая дляapp.jsиrows.jsбез цикла импортов. Файлы:address.js,mark.js(new),editors/rows.js,app.js. - [M10.3] Панель скрытых полей: исключение настроек по бэку, диспатч по типу, показ ЗНАЧЕНИЙ лексикона. Три правки: (1) Утечка полей таба «Настройки» (hide_section, «копировать содержимое» и т.п.) — хардкод
STRUCTURALбыл неполон. Теперьfields/types(FieldTypesHandler) дополнительно отдаётsettings= имена полей таба «Настройки» (formtabs index 0) из живого mpc_base; фронт исключает их (S.settingsFields,isSettingsField). (2) Раньше все скрытые поля рисовались как input/textarea. Теперь контрол выбирается по ТИПУ поля (controlHtml/wireControlвpanels.js):image→ превью + загрузка файла (значение-путь,image/upload);richtext→ contenteditable (форматирование видно); прочее → input/textarea. (3) В лексиконном режиме конфиг хранит КЛЮЧ — панель показывала ключ (а сохранение без правки записало бы сам ключ в значение лексикона — порча). Теперьconfig/getдополнительно отдаёт картыlexiconsпо уровням (resource/global), фронт показывает ЗНАЧЕНИЕ (lexValue); запись прежним путём — бэк пишет значение в лексикон под существующим ключом (round-trip корректен). Бэк:LexiconWriter::all()(дамп key→value),FieldWriter::readLexicons(),Mpcve::readLexicons(),ConfigGetHandler. Фронт:state.js(+settingsFields/lexicons),app.js/api.js(приём),panels.js,overlay.css(.mpcve-hpanel__rte/__img/__thumb). - [M10.2] Скрытые поля СЕКЦИИ — обобщение с фикс-набора стилей на вычисляемый диф. Раньше панель секции показывала фиксированно
inline_styles/class_names. Теперь: показываем стилевые поля (всегда, как раньше — каскад resource-first) ПЛЮС скрытые КОНТЕНТНЫЕ поля = ключи config-объекта секции МИНУС весь таб «Настройки»/служебные/css_file_path(=STRUCTURAL, стили вынесены отдельной веткой) МИНУС ВИДИМЫЕ (имена из[data-mpc-field]/[data-mpc-rfield]/[data-mpc-tv]этой секции). Уровень: контентstatic→globalиначеresource; стили — resource-first (каскад M10.1). v1 — скаляры; скрытые migx-списки/медиа пропускаются (нет DOM-контейнера для построчного редактора, реально


Последние обсуждения в сообществе MODX.pro