(function () {
'use strict';
const CONTENT_ROUTE_RE =
/^\/teach\/control\/stream\/view\/id\/\d+\/?$/;
const TRAINING_ADMIN_ROUTES = [
/^\/teach\/control\/stream\/view\/id\/\d+\/?$/,
/^\/teach\/control\/stream\/update\/id\/\d+\/?$/,
/^\/teach\/control\/stream\/access\/id\/\d+\/?$/,
/^\/pl\/teach\/training\/showcase\/?$/,
/^\/pl\/teach\/training\/lessons-schedule\/?$/,
/^\/teach\/control\/stat\/stream\/id\/\d+\/?$/,
/^\/teach\/control\/stream\/stat\/id\/\d+\/?$/,
/^\/pl\/teach\/training\/achievements\/?$/,
/^\/pl\/teach\/training\/chatium\/?$/,
/^\/pl\/teach\/training\/feedback\/?$/
];
function isAdmin() {
return window.userInfo?.isAdmin === true;
}
function isTrainingAdminRoute() {
return TRAINING_ADMIN_ROUTES.some(re => re.test(location.pathname));
}
function isContentRoute() {
return CONTENT_ROUTE_RE.test(location.pathname);
}
function isTargetPage() {
return (
isTrainingAdminRoute() &&
isAdmin() &&
document.querySelector('.page-header')
);
}
if (isTrainingAdminRoute() && isAdmin()) {
document.documentElement.classList.add(
'pt-admin-inside-loading'
);
if (!document.getElementById('pt-admin-fouc-guard')) {
const style = document.createElement('style');
style.id = 'pt-admin-fouc-guard';
style.textContent = `
html.pt-admin-inside-loading
body .main-page-block > .container {
visibility: hidden !important;
opacity: 0 !important;
}
html.pt-admin-inside-loading
body .main-page-block > .container *,
html.pt-admin-inside-loading
body .main-page-block > .container *::before,
html.pt-admin-inside-loading
body .main-page-block > .container *::after {
animation: none !important;
transition: none !important;
}
`;
document.head.appendChild(style);
}
}
function cleanText(value) {
return String(value || '').replace(/\s+/g, ' ').trim();
}
function iconBook() {
return `<svg viewBox="0 0 24 24" fill="none" aria-hidden="true"><path d="M5.5 4.75A2.75 2.75 0 0 1 8.25 2h10.25v16.25H8.25A2.75 2.75 0 0 0 5.5 21V4.75Z" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"/><path d="M5.5 18.25A2.75 2.75 0 0 1 8.25 15.5H18.5" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"/></svg>`;
}
function iconFolder() {
return `<svg viewBox="0 0 24 24" fill="none" aria-hidden="true"><path d="M3.5 7.25A2.25 2.25 0 0 1 5.75 5h4l2 2h6.5a2.25 2.25 0 0 1 2.25 2.25v7A2.75 2.75 0 0 1 17.75 19h-12A2.75 2.75 0 0 1 3 16.25V7.75c0-.17.02-.34.05-.5" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"/></svg>`;
}
function iconArrow() {
return `<svg viewBox="0 0 24 24" fill="none" aria-hidden="true"><path d="M5 12h14M14 7l5 5-5 5" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>`;
}
function ensureSectionHeader(target, type, count) {
if (!target || target.querySelector(':scope > .pt-section-head')) return;
const header = document.createElement('div');
header.className = 'pt-section-head';
header.innerHTML = `
<div class="pt-section-head__title">
<span class="pt-section-head__icon">${type === 'lessons' ? iconBook() : iconFolder()}</span>
<span>${type === 'lessons' ? 'Уроки' : 'Подтренинги'}</span>
</div>
<span class="pt-section-head__count">${count}</span>
`;
target.insertBefore(header, target.firstChild);
}
function decorateBreadcrumbs() {
const native = document.querySelector(
'.main-page-block > .container > .breadcrumbs'
);
if (
!native ||
document.querySelector(
'.pt-breadcrumbs'
)
) {
return;
}
const custom =
document.createElement(
'nav'
);
custom.className =
'pt-breadcrumbs';
custom.setAttribute(
'aria-label',
'Хлебные крошки'
);
Array.from(
native.childNodes
).forEach(
node => {
custom.appendChild(
node.cloneNode(true)
);
}
);
native.classList.add(
'pt-native-breadcrumbs'
);
native.parentNode.insertBefore(
custom,
native
);
}
function decorateTags() {
const tagRoot =
document.querySelector(
'.page-header .gc-tags-editable'
);
if (!tagRoot) {
return;
}
const host =
tagRoot.parentElement;
if (
!host ||
host.querySelector(
':scope > .pt-tags'
)
) {
return;
}
const raw =
tagRoot.getAttribute(
'data-tags'
) || '';
const tags =
raw
.split(',')
.map(cleanText)
.filter(Boolean);
const view =
document.createElement(
'div'
);
view.className =
'pt-tags';
tags.forEach(
tag => {
const pill =
document.createElement(
'span'
);
pill.className =
'pt-tag';
pill.textContent =
tag;
view.appendChild(
pill
);
}
);
const edit =
document.createElement(
'button'
);
edit.type =
'button';
edit.className =
'pt-tags__edit';
edit.textContent =
tags.length ? 'Изменить' : 'Добавить тег';
if (!tags.length) {
view.classList.add('pt-tags--empty');
}
edit.addEventListener(
'click',
function () {
host.classList.add(
'pt-tags-editing'
);
window.setTimeout(
function () {
const clickable =
tagRoot.querySelector(
'a, button, [role="button"]'
);
if (clickable) {
clickable.click();
} else {
tagRoot.click();
}
},
30
);
}
);
view.appendChild(
edit
);
host.classList.add(
'pt-tags-host'
);
host.insertBefore(
view,
tagRoot
);
}
function decorateHeader() {
const pageHeader = document.querySelector('.page-header');
if (!pageHeader || pageHeader.classList.contains('pt-header-ready')) return;
pageHeader.classList.add('pt-header-ready');
const heading = pageHeader.querySelector('h1');
if (heading) {
const eyebrow = document.createElement('div');
eyebrow.className = 'pt-header__eyebrow';
eyebrow.textContent = 'Управление тренингом';
heading.parentNode.insertBefore(eyebrow, heading);
}
const pageActions = pageHeader.querySelector('.page-actions');
if (pageActions) {
pageActions.classList.add('pt-header__actions');
const groups = pageActions.querySelectorAll(':scope > .btn-group');
if (groups[0]) groups[0].classList.add('pt-actions-secondary');
if (groups[1]) groups[1].classList.add('pt-actions-primary');
}
const contentMenu = pageHeader.querySelector('.content-menu');
if (contentMenu) contentMenu.classList.add('pt-tabs');
}
function iconChat() {
return `<svg viewBox="0 0 24 24" fill="none" aria-hidden="true"><path d="M5 5.5h14a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2h-7l-4.5 3v-3H5a2 2 0 0 1-2-2v-8a2 2 0 0 1 2-2Z" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round"/></svg>`;
}
function getTrainingId() {
const marker =
document.querySelector(
'[id^="training-id-"]'
);
const markerMatch =
marker?.id?.match(
/training-id-(\d+)/
);
if (markerMatch) {
return markerMatch[1];
}
const pathMatch =
location.pathname.match(
/\/stream\/view\/id\/(\d+)/
);
return pathMatch?.[1] || '';
}
function getChatSourceMeta() {
const source =
document.querySelector(
'.psych-training-chat-source'
);
if (!source) {
return null;
}
const block =
source.closest(
'.xdget-block[data-xdget-id]'
);
const link =
source.querySelector(
'a[href]'
);
if (!block) {
return null;
}
const contentHost =
block.querySelector(
'.html-content'
);
return {
source,
block,
blockId:
cleanText(
block.getAttribute(
'data-xdget-id'
)
),
href:
cleanText(
link?.getAttribute(
'href'
)
),
label:
cleanText(
link?.textContent
) || 'Чат программы',
rawHtml:
contentHost
? contentHost.innerHTML
: source.outerHTML
};
}
function getTrainingEditorUrl() {
const id =
getTrainingId();
if (!id) {
return '#';
}
return (
'/teach/control/stream/view/id/' +
encodeURIComponent(id) +
'/editMode/1'
);
}
function parseSettingsFormHtml(html) {
const doc =
new DOMParser().parseFromString(
`<form id="pt-temp-form">${html || ''}</form>`,
'text/html'
);
const form =
doc.querySelector(
'#pt-temp-form'
);
const payload = {};
if (!form) {
return payload;
}
form.querySelectorAll(
'[name]'
).forEach(control => {
const name =
control.getAttribute(
'name'
);
if (!name) {
return;
}
const tag =
control.tagName.toLowerCase();
const type =
cleanText(
control.getAttribute(
'type'
)
).toLowerCase();
if (
(
type === 'checkbox' ||
type === 'radio'
) &&
!control.checked
) {
return;
}
if (
tag === 'select' &&
control.multiple
) {
payload[name] =
Array.from(
control.selectedOptions
).map(option => option.value);
return;
}
payload[name] =
control.value ?? '';
});
return payload;
}
function gcAjax(
url,
data = {}
) {
return new Promise(
(resolve, reject) => {
if (
typeof window.ajaxCall ===
'function'
) {
try {
window.ajaxCall(
url,
data,
{},
response => resolve(response),
error => reject(error)
);
return;
} catch (error) {
reject(error);
return;
}
}
if (
window.jQuery?.ajax
) {
window.jQuery.ajax({
url,
type: 'POST',
data,
dataType: 'json',
success: resolve,
error:
(_, __, error) =>
reject(
error ||
new Error(
'AJAX error'
)
)
});
return;
}
reject(
new Error(
'GetCourse AJAX helper unavailable'
)
);
}
);
}
function isSafeChatUrl(value) {
const raw =
cleanText(value);
if (!raw) {
return false;
}
try {
const url =
new URL(
raw,
window.location.origin
);
return (
url.protocol === 'http:' ||
url.protocol === 'https:'
);
} catch (error) {
return false;
}
}
function setChatAdminStatus(
message,
type = 'info'
) {
const status =
document.querySelector(
'.pt-chat-admin__status'
);
if (!status) {
return;
}
status.textContent =
message;
status.className =
'pt-chat-admin__status ' +
`is-${type}`;
status.hidden = false;
window.clearTimeout(
setChatAdminStatus.timer
);
setChatAdminStatus.timer =
window.setTimeout(
function () {
status.hidden = true;
},
4400
);
}
function buildUpdatedChatHtml(
meta,
href
) {
const holder =
document.createElement(
'div'
);
holder.innerHTML =
meta.rawHtml ||
meta.source.outerHTML;
const source =
holder.querySelector(
'.psych-training-chat-source'
);
if (!source) {
throw new Error(
'Маркер psych-training-chat-source не найден'
);
}
let link =
source.querySelector(
'a[href]'
);
if (!link) {
link =
document.createElement(
'a'
);
link.textContent =
meta.label ||
'Чат программы';
source.appendChild(
link
);
}
link.setAttribute(
'href',
href
);
if (
!cleanText(
link.textContent
)
) {
link.textContent =
'Чат программы';
}
return holder.innerHTML;
}
async function saveTrainingChatLink(
button
) {
const meta =
getChatSourceMeta();
if (!meta) {
setChatAdminStatus(
'HTML-блок psych-training-chat-source не найден.',
'error'
);
return;
}
const input =
document.querySelector(
'.pt-chat-admin__input'
);
const href =
cleanText(
input?.value
);
if (
!isSafeChatUrl(
href
)
) {
setChatAdminStatus(
'Укажите корректную ссылку http(s) или относительный адрес.',
'error'
);
input?.focus();
return;
}
const originalText =
button.textContent;
button.disabled = true;
button.textContent =
'Сохраняю…';
setChatAdminStatus(
'Сохраняю ссылку в HTML-блок GetCourse…'
);
try {
const settingsResponse =
await gcAjax(
'/pl/lite/block/settings?id=' +
encodeURIComponent(
meta.blockId
),
{
place: 'content'
}
);
const settingsHtml =
settingsResponse?.data?.html ||
settingsResponse?.html ||
'';
const payload =
parseSettingsFormHtml(
settingsHtml
);
payload[
'blockParam[rawValue]'
] =
buildUpdatedChatHtml(
meta,
href
);
if (
!(
'blockParam[replaceVariables]' in
payload
)
) {
payload[
'blockParam[replaceVariables]'
] = '0';
}
const saveResponse =
await gcAjax(
'/pl/lite/block/save-settings?id=' +
encodeURIComponent(
meta.blockId
),
payload
);
if (
saveResponse &&
saveResponse.success === false
) {
throw new Error(
saveResponse.message ||
saveResponse.error ||
'GetCourse отклонил сохранение'
);
}
const sourceLink =
meta.source.querySelector(
'a'
);
if (sourceLink) {
sourceLink.setAttribute(
'href',
href
);
}
setChatAdminStatus(
'Ссылка на чат сохранена.',
'success'
);
} catch (error) {
console.error(
'[training-admin-inside] ' +
'Не удалось сохранить ссылку на чат:',
error
);
setChatAdminStatus(
'Не удалось сохранить ссылку' +
(
error?.message
? `: ${error.message}`
: '.'
),
'error'
);
} finally {
button.disabled = false;
button.textContent =
originalText;
}
}
function buildChatAdminCard() {
if (
document.querySelector(
'.pt-chat-admin'
)
) {
return;
}
const root =
document.querySelector(
'.xdget-root > .xdget-training'
);
if (!root) {
return;
}
const meta =
getChatSourceMeta();
const card =
document.createElement(
'section'
);
card.className =
'pt-chat-admin';
const editorUrl =
getTrainingEditorUrl();
if (!meta) {
card.innerHTML = `
<div class="pt-chat-admin__icon">
${iconChat()}
</div>
<div class="pt-chat-admin__content">
<div class="pt-chat-admin__eyebrow">
Сервисы тренинга
</div>
<h2>Чат программы</h2>
<p>
Добавьте HTML-блок с классом
<code>psych-training-chat-source</code>
и настройте его видимость средствами GetCourse.
</p>
<a class="pt-chat-admin__secondary"
href="${editorUrl}">
Открыть редактор
</a>
</div>
`;
} else {
card.innerHTML = `
<div class="pt-chat-admin__icon">
${iconChat()}
</div>
<div class="pt-chat-admin__content">
<div class="pt-chat-admin__eyebrow">
Сервисы тренинга
</div>
<h2>Чат программы</h2>
<p>
Ссылка показывается ученику только тогда,
когда HTML-блок доступен ему по правилам
видимости GetCourse.
</p>
<div class="pt-chat-admin__form">
<label>
Ссылка на чат
</label>
<div class="pt-chat-admin__field-row">
<input
type="url"
class="pt-chat-admin__input"
value=""
placeholder="https://... или /c/12345"
autocomplete="off"
spellcheck="false"
>
<button
type="button"
class="pt-chat-admin__save"
>
Сохранить
</button>
</div>
<div class="pt-chat-admin__meta">
<span>
Блок:
<strong>${meta.blockId}</strong>
</span>
<a href="${editorUrl}">
Настроить видимость
</a>
</div>
<div
class="pt-chat-admin__status"
hidden
></div>
</div>
</div>
`;
const input =
card.querySelector(
'.pt-chat-admin__input'
);
input.value =
meta.href || '';
const save =
card.querySelector(
'.pt-chat-admin__save'
);
save.addEventListener(
'click',
() =>
saveTrainingChatLink(
save
)
);
input.addEventListener(
'keydown',
event => {
if (
event.key === 'Enter'
) {
event.preventDefault();
save.click();
}
}
);
}
const firstContent =
root.querySelector(
':scope > .pt-content-title'
) ||
root.firstChild;
root.insertBefore(
card,
firstContent
);
const sourceBlock =
meta?.block;
if (sourceBlock) {
sourceBlock.classList.add(
'psych-training-chat-source-admin-block'
);
}
}
function addContentHeading() {
const root = document.querySelector('.xdget-root > .xdget-training');
if (!root || root.querySelector(':scope > .pt-content-title')) return;
const heading = document.createElement('div');
heading.className = 'pt-content-title';
heading.innerHTML = `<div><h2>Содержание курса</h2><p>Уроки и вложенные тренинги</p></div>`;
root.insertBefore(heading, root.firstChild);
}
function decorateLessons() {
document.querySelectorAll('.xdget-training > .xdget-row .xdget-lessonList').forEach(block => {
const rows = Array.from(block.querySelectorAll('.lesson-list > li[data-lesson-id]'));
ensureSectionHeader(block, 'lessons', rows.length);
rows.forEach((row, index) => {
row.classList.add(
'pt-lesson-row'
);
const watched =
row.querySelector(
'.lesson-list__video-watched'
);
if (watched) {
watched.setAttribute(
'aria-hidden',
'true'
);
}
const imageCell =
row.querySelector(
'.item-image'
);
let hasCustomImage = false;
if (imageCell) {
const inlineBackground =
imageCell.style.backgroundImage ||
'';
const computedBackground =
window.getComputedStyle(
imageCell
).backgroundImage ||
'';
const backgroundImage =
inlineBackground &&
inlineBackground !== 'none'
? inlineBackground
: computedBackground;
const urlMatch =
backgroundImage.match(
/url\(["']?(.*?)["']?\)/
);
if (
urlMatch &&
urlMatch[1]
) {
hasCustomImage = true;
row.classList.add(
'pt-lesson-row--with-image'
);
const itemLink =
row.querySelector(
':scope > .item-a'
);
if (
itemLink &&
!itemLink.querySelector(
':scope > .pt-lesson-media'
)
) {
const media =
document.createElement(
'span'
);
media.className =
'pt-lesson-media';
const img =
document.createElement(
'img'
);
img.className =
'pt-lesson-media__img';
img.src =
urlMatch[1];
img.alt = '';
media.appendChild(
img
);
itemLink.insertBefore(
media,
itemLink.firstChild
);
}
imageCell.classList.add(
'pt-native-image'
);
}
}
if (!hasCustomImage) {
row.classList.add(
'pt-lesson-row--numbered'
);
const itemLink =
row.querySelector(
':scope > .item-a'
);
if (
itemLink &&
!itemLink.querySelector(
':scope > .pt-lesson-number'
)
) {
const number =
document.createElement(
'span'
);
number.className =
'pt-lesson-number';
number.textContent =
String(index + 1);
itemLink.insertBefore(
number,
itemLink.firstChild
);
}
}
});
const form = block.querySelector('form');
if (form && rows.length === 0 && !form.querySelector('.pt-empty')) {
const empty = document.createElement('div');
empty.className = 'pt-empty';
empty.innerHTML = `<span class="pt-empty__icon">${iconBook()}</span><span class="pt-empty__title">Уроков пока нет</span><span class="pt-empty__text">Добавьте первый урок через кнопку «Добавить».</span>`;
form.appendChild(empty);
}
});
}
function decorateSubtrainings() {
document.querySelectorAll(
'.xdget-training .xdget-trainingList'
).forEach(block => {
const rows = Array.from(
block.querySelectorAll(
'.stream-table .training-row'
)
);
if (!rows.length) return;
block.classList.add(
'pt-subtrainings'
);
ensureSectionHeader(
block,
'subtrainings',
rows.length
);
rows.forEach(row => {
if (
row.classList.contains(
'pt-subcard-ready'
)
) {
return;
}
const link =
row.querySelector(
'td > a[href]'
);
const title =
row.querySelector(
'.stream-title'
);
if (!link || !title) {
return;
}
row.classList.add(
'pt-subcard-ready'
);
link.classList.add(
'pt-subcard'
);
const metaNode =
link.querySelector('div');
const meta =
metaNode
? cleanText(
metaNode.textContent
)
: '';
const titleText =
cleanText(
title.textContent
);
link.innerHTML = `
<span class="pt-subcard__icon">
${iconFolder()}
</span>
<span class="pt-subcard__content">
<span class="pt-subcard__badge">
Подтренинг
</span>
<span class="pt-subcard__title"></span>
<span class="pt-subcard__meta"></span>
</span>
<span class="pt-subcard__arrow">
${iconArrow()}
</span>
`;
link.querySelector(
'.pt-subcard__title'
).textContent =
titleText;
link.querySelector(
'.pt-subcard__meta'
).textContent =
meta ||
'Вложенная программа';
});
});
}
function releasePreload(active) {
const reveal = function () {
if (active) {
document.documentElement.classList.add(
'pt-admin-inside-ready',
'pt-admin-inside-settling'
);
document.body.classList.add(
'pt-admin-inside-ready',
'pt-admin-inside-settling'
);
}
document.documentElement.classList.remove(
'pt-admin-inside-loading'
);
const guard = document.getElementById(
'pt-admin-fouc-guard'
);
if (guard) {
guard.remove();
}
if (active) {
window.setTimeout(
function () {
document.documentElement.classList.remove(
'pt-admin-inside-settling'
);
document.body.classList.remove(
'pt-admin-inside-settling'
);
},
180
);
}
};
if (!active) {
reveal();
return;
}
window.requestAnimationFrame(function () {
window.requestAnimationFrame(reveal);
});
}
let initialized = false;
function init() {
if (initialized || !isTargetPage()) return;
initialized = true;
document.documentElement.classList.add('pt-admin-inside');
document.body.classList.add('pt-admin-inside');
const path = location.pathname;
const tabClass =
isContentRoute() ? 'pt-tab-content' :
/^\/teach\/control\/stream\/update\//.test(path) ? 'pt-tab-settings' :
/^\/teach\/control\/stream\/access\//.test(path) ? 'pt-tab-access' :
/^\/pl\/teach\/training\/showcase\/?$/.test(path) ? 'pt-tab-sale' :
/^\/pl\/teach\/training\/lessons-schedule\/?$/.test(path) ? 'pt-tab-schedule' :
/^\/teach\/control\/stat\/stream\//.test(path) ? 'pt-tab-students' :
/^\/teach\/control\/stream\/stat\//.test(path) ? 'pt-tab-statistics' :
/^\/pl\/teach\/training\/achievements\/?$/.test(path) ? 'pt-tab-achievements' :
/^\/pl\/teach\/training\/feedback\/?$/.test(path) ? 'pt-tab-feedback' :
/^\/pl\/teach\/training\/chatium\/?$/.test(path) ? 'pt-tab-app' :
'';
if (tabClass) {
document.documentElement.classList.add(tabClass);
document.body.classList.add(tabClass);
}
decorateBreadcrumbs();
decorateHeader();
decorateTags();
if (isContentRoute()) {
addContentHeading();
buildChatAdminCard();
decorateLessons();
decorateSubtrainings();
window.setTimeout(
function () {
decorateHeader();
decorateTags();
buildChatAdminCard();
decorateLessons();
decorateSubtrainings();
},
20
);
window.setTimeout(
decorateSubtrainings,
350
);
window.setTimeout(
decorateSubtrainings,
900
);
}
window.setTimeout(
function () {
releasePreload(true);
},
40
);
}
function scheduleInit() {
init();
window.setTimeout(init, 250);
window.setTimeout(init, 700);
window.setTimeout(init, 1400);
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', scheduleInit, { once: true });
} else {
scheduleInit();
}
if (isTrainingAdminRoute() && isAdmin()) {
window.setTimeout(function () {
if (!document.documentElement.classList.contains('pt-admin-inside-ready')) {
releasePreload(false);
}
}, 2200);
}
})();
