PageSourceSearch

https://discours.netlify.app/scripts/editor-shared-6544de93.js

js discours.netlify.app collected 2026-10-03 10:40:03 UTC 340,545 bytes, 1 lines download raw bytes

1(globalThis.webpackChunkdiscours_io_frontend=globalThis.webpackChunkdiscours_io_frontend||[]).push([[18],{6929(t,e,n){"use strict";var i=n(84799),s=n(88484);angular.module("discours.components").component("editorHeader",{templateUrl:"/components/editorHeader/editorHeader.html",bindings:{docTitle:"<",slug:"<",collabStatus:"<",connectedUsers:"<",isOfflineMode:"<",isReconnecting:"<",collabPermissionDenied:"<",onRetryConnection:"&?",tiptapEditor:"<",isPreviewMode:"<",suggestionMode:"<",sidebarVisible:"<",pendingCount:"<",collabSyncPending:"<",contentStatus:"<",contentLink:"<?",isSaving:"<",isPublishing:"<",hasUnsavedChanges:"<",hasDraft:"<",canEdit:"<",isAuthor:"<",showCollabControls:"<",isSavingVersion:"<",isSaveSuccess:"<",isSaveError:"<",lastVersionSavedAt:"<",showInviteButton:"<",inviteCount:"<",onOpenInvite:"&?",onModeChange:"&",onToggleSidebar:"&",onSave:"&?",onSaveVersionDialog:"&?",onPublish:"&?",onValidateBeforePublish:"&?",onVersionsClick:"&?",nightMode:"<",onToggleNightMode:"&?",marginViewMode:"<",onToggleMarginView:"&?",quietMode:"<",onToggleQuietMode:"&?",onRunTypographer:"&?",onOpenSettings:"&?",isAdmin:"<",onCleanCode:"&?",wordCount:"<",charCount:"<",initialParaCount:"<?",initialLastModified:"<",isLoggedIn:"<",userAvatar:"<",userPath:"<"},controller:["$scope","$element","$window","$timeout","$location","$injector","ConfirmDialog","Notification","eventBus",function(t,e,a,o,l,r,c,d,g){function p(t){let e=0;return t.forEach(function(t){"paragraph"===t.type.name&&t.textContent.trim().length>0&&e++}),e}function u(){(_.profileMenuOpen||B)&&(R&&(clearTimeout(R),R=null),B&&(B(),B=null),_.profileMenuOpen=!1,U&&"function"==typeof U.focus&&!U.disabled&&document.contains(U)&&U.focus(),U=null,t.$$destroyed||t.$applyAsync())}function m(){try{var t=_.tiptapEditor;if(t&&"function"==typeof t.getHTML){var e=t.getHTML();e&&"<p></p>"!==e&&localStorage.setItem("ls.contentItem.body",JSON.stringify(e))}_.docTitle&&localStorage.setItem("ls.contentItem.title",JSON.stringify(_.docTitle))}catch(t){}}function v(){let t;try{t=_.onPublish()}catch(t){return}t&&"function"==typeof t.then?t.then(function(t){t&&_.navigateToPublished()}).catch(function(t){}):_.navigateToPublished()}function b(){const t=_.contentStatus;return"posted"===t||"published"===t||"featured"===t||"recalled"===t}function f(){_.tiptapEditor.commands.enableLanguageTool&&(_.ltEnabled=!1!==_.tiptapEditor.commands.enableLanguageTool())}function h(){t.$$destroyed||(_.typographerDone=!0,T&&o.cancel(T),T=o(function(){_.typographerDone=!1},4e3))}function w(t){return t?("0"+t.getDate()).slice(-2)+"."+("0"+(t.getMonth()+1)).slice(-2)+"."+String(t.getFullYear()).slice(-2)+" в "+("0"+t.getHours()).slice(-2)+":"+("0"+t.getMinutes()).slice(-2):null}function y(){if(!_.slug&&!_.contentStatus)return _.statusText="Черновик",_.statusClass="draft",_.publishButtonText="Опубликовать",void(_.toolsPublishText="Опубликовать");switch(_.contentStatus){case"draft":_.statusText="Черновик",_.statusClass="draft",_.publishButtonText="Опубликовать",_.toolsPublishText="Опубликовать";break;case"posted":case"recalled":_.statusText="Опубликовано",_.statusClass="posted",_.publishButtonText="Сохранить",_.toolsPublishText="Обновить публикацию";break;case"published":case"featured":_.statusText="В журнале",_.statusClass="featured",_.publishButtonText="Сохранить",_.toolsPublishText="Обновить публикацию";break;default:_.statusText=null,_.statusClass="",_.publishButtonText="Опубликовать",_.toolsPublishText="Опубликовать"}}const _=this;_.isMac="undefined"!=typeof navigator&&(/macOS/i.test(navigator.userAgentData&&navigator.userAgentData.platform||"")||/Mac|iPod|iPhone|iPad/.test(navigator.platform||"")),_.isScrolled=!1,_.modeSwitcherOpen=!1,_.currentMode="editing",_.showCollabStatus=!1,_.showPanelButton=!1,_.showSuggestionMode=!1,_._publishDialogOpen=!1,_.helpBtnHidden=!1,_.profileMenuOpen=!1,_.avatarLoadError=!1,_.toolsPanelOpen=!1,_.typographerDone=!1,_.cleanCodeDone=!1,_.statusDescription="",_.paraCount=null,_.lastModifiedAt=null,_.lastModifiedFormatted=null,_.ltChecking=!1;let k=null,C=null;_.ltEnabled=!1,_.showHelpDot=!1;try{_.showHelpDot=!localStorage.getItem("discours_shortcuts_help_seen")}catch(t){}let x=null,S=null,I=null,M=null,P=null,E=null,L=null,T=null,A=null,D=null,V=null,H=null;_.$onInit=function(){function e(){const t=_.tiptapEditor&&_.tiptapEditor.storage&&_.tiptapEditor.storage.languageTool;_.ltChecking=!(!t||!t.loading),_.ltRateLimited=!(!t||!t.rateLimitError)}_.updateCurrentMode(),_.updateCollabFeatures(),y(),_.statusDescription=_.getStatusDescription(),angular.element(a).on("scroll",_.handleScroll),_._onShortcutsHelpSeen=function(){t.$applyAsync(function(){_.showHelpDot=!1})},a.addEventListener("shortcutsHelpSeen",_._onShortcutsHelpSeen),t.$watch(function(){const t=_.tiptapEditor&&_.tiptapEditor.storage&&_.tiptapEditor.storage.languageTool;return t?(t.loading?1:0)+(t.rateLimitError?2:0):null}
1,e),_._onLtLoadingChange=function(){t.$applyAsync(e)},document.addEventListener("lt-loading-change",_._onLtLoadingChange),t.$watch(function(){return _.tiptapEditor&&_.tiptapEditor.isEditable},function(t,e){t!==e&&_.updateCurrentMode()})},_.$onChanges=function(e){if((e.isPreviewMode||e.suggestionMode||e.tiptapEditor)&&_.updateCurrentMode(),e.tiptapEditor){var n=e.tiptapEditor.previousValue,i=e.tiptapEditor.currentValue;k&&n&&n.off&&n.off("update",k),C&&(clearTimeout(C),C=null),i&&i.on&&function(e){if(e.state&&e.state.doc){var n=p(e.state.doc);(n>0||null==_.paraCount)&&(_.paraCount=n)}k=function(e){e&&e.transaction&&e.transaction.docChanged&&(C&&clearTimeout(C),C=setTimeout(function(){C=null;var e=_.tiptapEditor;if(e&&e.state&&e.state.doc){var n=p(e.state.doc);t.$applyAsync(function(){_.lastModifiedAt=new Date,_.lastModifiedFormatted=w(_.lastModifiedAt),_.paraCount=n})}},300))},e.on("update",k)}(i)}if(e.initialParaCount&&null!=e.initialParaCount.currentValue&&null==_.paraCount&&(_.paraCount=e.initialParaCount.currentValue),e.initialLastModified&&e.initialLastModified.currentValue&&!_.lastModifiedAt&&(_.lastModifiedAt=e.initialLastModified.currentValue,_.lastModifiedFormatted=w(_.lastModifiedAt)),(e.collabStatus||e.isOfflineMode||e.isReconnecting||e.collabPermissionDenied||e.showCollabControls)&&(_.statusDescription=_.getStatusDescription(),_._statusTitleCache=_.getStatusTitle()),e.lastVersionSavedAt){const t=e.lastVersionSavedAt.currentValue;_._savedTimeCache=t?new Date(t).toLocaleTimeString():""}e.showCollabControls&&_.updateCollabFeatures(),(e.contentStatus||e.slug)&&y()};var B=null,R=null,U=null;_.openProfileMenu=function(){if(_.profileMenuOpen)u();else{U=document.activeElement,_.profileMenuOpen=!0,B&&(B(),B=null);var e=function(){B&&B(),u()},n=function(t){"Escape"!==t.key&&27!==t.keyCode||(B&&B(),u())};B=function(){document.removeEventListener("click",e,!0),document.removeEventListener("keyup",n)},R=setTimeout(function(){R=null,!t.$$destroyed&&_.profileMenuOpen?(document.addEventListener("click",e,!0),document.addEventListener("keyup",n)):B&&(B(),B=null)},0)}},_.openLogin=function(){if(_.tiptapEditor&&m(),"function"==typeof a.loadAuthModals){var e;try{e=r.get("$ocLazyLoad")}catch(t){return void(a.location.href="/#login")}a.loadAuthModals().then(function(n){if(!t.$$destroyed)return e.inject((n&&n.default||n||{}).modules||["discours.components","discours.templates"])}).then(function(){t.$$destroyed||(t.$root.editorLoginDetail={title:"Войдите, чтобы сохранить черновик на будущее"},t.$root.editorLoginShown=!0,t.$applyAsync())}).catch(function(){t.$$destroyed||(m(),a.location.href="/#login")})}else a.location.href="/#login"},_.logOut=function(){var t=r.get("loginService"),e=r.get("$route");t.logout().catch(function(t){}).finally(function(){e.reload()})},_.$onDestroy=function(){R&&(clearTimeout(R),R=null),B&&(B(),B=null),angular.element(a).off("scroll",_.handleScroll),x&&o.cancel(x),S&&o.cancel(S),I&&(o.cancel(I),I=null),t.$root.editorLoginShown&&(t.$root.editorLoginShown=!1,t.$root.editorLoginDetail=null),D&&(clearTimeout(D),D=null),_.removeToolsPanelHandlers(),T&&o.cancel(T),A&&o.cancel(A),_._onShortcutsHelpSeen&&a.removeEventListener("shortcutsHelpSeen",_._onShortcutsHelpSeen),_._onLtLoadingChange&&document.removeEventListener("lt-loading-change",_._onLtLoadingChange),V&&(clearTimeout(V),V=null),C&&(clearTimeout(C),C=null),k&&_.tiptapEditor&&_.tiptapEditor.off&&(_.tiptapEditor.off("update",k),k=null),H=null},_.updateCollabFeatures=function(){const t=!0===_.showCollabControls||"true"===_.showCollabControls;_.showCollabStatus=t,_.showPanelButton=t,_.showSuggestionMode=t},_.updateCurrentMode=function(){_.currentMode=_.isPreviewMode||_.tiptapEditor&&!1===_.tiptapEditor.isEditable?"preview":_.suggestionMode?"suggesting":"editing"},_.toggleModeSwitcher=function(){_.modeSwitcherOpen=!_.modeSwitcherOpen},_.closeModeSwitcherDelayed=function(){x&&o.cancel(x),x=o(function(){_.modeSwitcherOpen=!1},150)},_.setMode=function(t){if(_.modeSwitcherOpen=!1,t===_.currentMode)return;const e=_.currentMode;_.currentMode=t,_.onModeChange&&!1===_.onModeChange({mode:t})&&(_.currentMode=e)},_.togglePreviewMode=function(){_.setMode("preview"===_.currentMode?"editing":"preview")}
1,_.openHelpModal=function(){_.closeToolsPanel(),g.emit(g.EVENTS.OPEN_FEEDBACK_MODAL,"bug")},_.openShortcutsHelp=function(){Promise.all([n.e(272),n.e(929),n.e(411),n.e(753),n.e(68),n.e(1)]).then(n.bind(n,38023)).then(function(e){e.openShortcutsHelp(),t.$applyAsync(function(){_.showHelpDot=!1})}).catch(function(t){})},_.openImportExport=function(){_.closeToolsPanel(),Promise.all([n.e(272),n.e(929),n.e(411),n.e(753),n.e(68),n.e(1)]).then(n.bind(n,11561)).then(function(t){let e=null,n=(0,i.q)();try{e=r.get("s3UploaderService")}catch(t){}try{n=r.get("THUMBOR_SERVER")||n}catch(t){}t.openImportExportDialog({editor:_.tiptapEditor,s3UploaderService:e,Notification:d,contentItem:{slug:_.slug||"draft",title:_.docTitle||""},THUMBOR_SERVER:n})}).catch(function(t){d.error("Не удалось открыть импорт/экспорт")})},_.toggleSidebar=function(){_.onToggleSidebar&&_.onToggleSidebar()},_.hasCursorPosition=function(t){if(!_.tiptapEditor)return!1;if(_.tiptapEditor.getProvider){const e=_.tiptapEditor.getProvider();if(e&&e.awareness){const n=e.awareness.getStates().get(t.clientId);if(n&&n.cursor&&null!=n.cursor.anchor)return!0}}return!(!_.tiptapEditor._cursorCache||null==_.tiptapEditor._cursorCache.get(t.clientId))},_.scrollToUserCursor=function(t){if(!_.tiptapEditor||!_.tiptapEditor.getProvider)return;const e=_.tiptapEditor.getProvider();if(!e||!e.awareness)return;const n=e.awareness.getStates().get(t.clientId);let i=null;if(n&&n.cursor&&null!=n.cursor.anchor&&_.tiptapEditor.resolveRelativePosition&&(i=_.tiptapEditor.resolveRelativePosition(n.cursor.anchor)),null==i&&(i=_.tiptapEditor._cursorCache&&_.tiptapEditor._cursorCache.get(t.clientId)),null!=i)try{const t=_.tiptapEditor.state.doc.content.size,e=Math.min(Math.max(0,i),t);_.tiptapEditor.chain().focus().setTextSelection(e).run(),V&&clearTimeout(V),V=setTimeout(function(){V=null;try{if(!_.tiptapEditor||!_.tiptapEditor.view)return;const t=_.tiptapEditor.view.coordsAtPos(e);t&&a.scrollTo({top:Math.max(0,a.pageYOffset+t.top-a.innerHeight/2),behavior:"smooth"})}catch(t){}},100)}catch(t){}},_.save=function(){_.showSaveDialog()},_.showSaveDialog=function(){const t=b();c.open({title:(0,s.t)(t?"Обновление материала":"Публикация материала"),message:(0,s.t)(t?"Материал будет обновлён и доступен читателям.":"Материал станет доступен читателям после публикации."),confirmText:(0,s.t)(t?"Обновить":"Опубликовать"),cancelText:(0,s.t)("Сохранить как черновик")}).then(function(){_.publish()}).catch(function(t){"cancel"!==t?c.onDismissed("[editorHeader.showSaveDialog]")(t):_.saveDraftAndGoToDrafts()})},_.saveDraftInPlace=function(){if(!_.isLoggedIn)return void _.openLogin();if(!_.onSave)return;const t=_.onSave();t&&"function"==typeof t.catch&&t.catch(function(){})},_.saveDraftAndGoToDrafts=function(){if(!_.isLoggedIn)return void _.openLogin();const e=_.getAfterSaveRedirectPath();if(!_.onSave)return void l.path(e);const n=_.onSave();n&&"function"==typeof n.then?n.then(function(){l.path(e),t.$applyAsync()}).catch(function(){}):n&&l.path(e)},_.getLastVersionSavedTooltip=function(){return _._savedTimeCache||""},_.publish=function(){if(_.onPublish&&!_.isPublishing&&!_._publishDialogOpen){if(_.onValidateBeforePublish)try{const t=_.onValidateBeforePublish();if(!1===t)return;if(t&&"function"==typeof t.then)return}catch(t){return}b()?v():(_._publishDialogOpen=!0,c.open({title:(0,s.t)("Опубликовать?"),message:(0,s.t)("После публикации материал станет доступен читателям"),confirmText:(0,s.t)("Опубликовать"),cancelText:(0,s.t)("Отмена")}).then(function(){v()},function(){c.onDismissed("[editorHeader.publish]")()}).catch(function(t){}).finally(function(){_._publishDialogOpen=!1}))}},_.navigateToPublished=function(){const t=_.getPublicUrl()||"/";if("/"===t)return void l.path("/");document.body.classList.add("is-navigating-from-editor");const e=document.createElement("div");e.id="editor-transition-overlay";const n=document.body.classList.contains("dark")?"#1e1e1e":"#fff";e.style.cssText="position:fixed;top:0;left:0;right:0;bottom:0;background:"+n+";z-index:var(--z-header-overlay, 100000);pointer-events:none;",document.body.appendChild(e),I&&o.cancel(I),I=o(function(){var t=document.getElementById("editor-transition-overlay");t&&t.remove(),document.body.classList.remove("is-navigating-from-editor")},8e3),S=o(function(){o.cancel(I),I=null,a.scrollTo(0,0),l.path(t)},0)},_.getPublicUrl=function(){if(_.contentLink&&"/"===_.contentLink.charAt(0))return _.contentLink;const t=l.path().replace(/^\/edit/,"").replace(/\/preview$/,"");
1return t&&/^\/(articles|expo)\//.test(t)?t:null},_.getAfterSaveRedirectPath=function(){if(("published"===_.contentStatus||"featured"===_.contentStatus)&&!1===_.isAuthor){const t=_.getPublicUrl();return t&&"/"!==t?t:"/drafts"}return"/drafts"},_.openInviteModal=function(){_.onOpenInvite&&_.onOpenInvite()},_.toggleToolsPanel=function(t){t&&t.stopPropagation(),_.toolsPanelOpen?_.closeToolsPanel():_.openToolsPanel()},_.openToolsPanel=function(){_.removeToolsPanelHandlers(),_.toolsPanelOpen=!0,L=o(function(){M=function(e){if(!_.toolsPanelOpen)return;const n=e.target;n&&n.closest&&n.closest(".editor-header__tools-wrapper")||t.$applyAsync(function(){_.closeToolsPanel()})},P=function(e){"Escape"!==e.key&&27!==e.keyCode||t.$applyAsync(function(){_.closeToolsPanel()})},E=function(){document.hidden&&_.toolsPanelOpen&&t.$applyAsync(function(){_.closeToolsPanel()})},document.addEventListener("mousedown",M,!0),document.addEventListener("touchstart",M,{capture:!0,passive:!0}),document.addEventListener("keydown",P,!0),document.addEventListener("visibilitychange",E)},0)},_.closeToolsPanel=function(){_.toolsPanelOpen=!1,_.removeToolsPanelHandlers()},_.removeToolsPanelHandlers=function(){M&&(document.removeEventListener("mousedown",M,!0),document.removeEventListener("touchstart",M,{capture:!0,passive:!0}),M=null),P&&(document.removeEventListener("keydown",P,!0),P=null),E&&(document.removeEventListener("visibilitychange",E),E=null),L&&(o.cancel(L),L=null)},_.toggleNightMode=function(){_.onToggleNightMode&&_.onToggleNightMode(),_.closeToolsPanel()},_.toggleMarginViewFromTools=function(){_.onToggleMarginView&&_.onToggleMarginView(),_.closeToolsPanel()},_.toggleQuietModeFromTools=function(){_.onToggleQuietMode&&_.onToggleQuietMode(),_.closeToolsPanel()},_.toggleLanguageTool=function(){if(!_.tiptapEditor)return;if(_.ltEnabled)return void(_.tiptapEditor.commands.disableLanguageTool&&(_.tiptapEditor.commands.disableLanguageTool(),_.ltEnabled=!1));let t=!1;try{t=!!localStorage.getItem("lt-privacy-accepted")}catch(t){}t?f():c.open({title:(0,s.t)("Проверка грамматики"),message:(0,s.t)("Для проверки орфографии и грамматики текст статьи будет отправлен на сервер проекта LanguageTool. Запустить?"),confirmText:(0,s.t)("Включить"),cancelText:(0,s.t)("Отмена")}).then(function(){try{localStorage.setItem("lt-privacy-accepted","1")}catch(t){}f()}).catch(function(){c.onDismissed("[editorHeader.toggleLanguageTool]")()})},_.runTypographer=function(){if(_.onRunTypographer){const e=_.onRunTypographer();e&&"function"==typeof e.then?e.catch(function(t){}).finally(function(){t.$$destroyed||(h(),t.$applyAsync())}):h()}},_.cleanHtmlContent=function(){_.tiptapEditor&&_.tiptapEditor.commands.cleanHtmlContent&&(_.tiptapEditor.commands.cleanHtmlContent({preserveEmptyParagraphs:!0,stripUnderline:!0}),_.onCleanCode&&_.onCleanCode(),_.cleanCodeDone=!0,A&&o.cancel(A),A=o(function(){_.cleanCodeDone=!1},4e3))},_.openSearchReplace=function(){_.tiptapEditor&&_.tiptapEditor.commands.openSearchReplace&&_.tiptapEditor.commands.openSearchReplace(!0)},_.openSettings=function(){_.closeToolsPanel(),_.onOpenSettings&&_.onOpenSettings()},_.retryConnection=function(){_.onRetryConnection&&_.onRetryConnection()},_.getLastModifiedFormatted=function(){return _.lastModifiedFormatted},_.statusText="Черновик",_.statusClass="draft",_.publishButtonText="Опубликовать",_.toolsPublishText="Опубликовать",_.getStatusTitle=function(){if(_.collabPermissionDenied)return"Только чтение";if(_.isOfflineMode)return"Автономный режим";switch(_.collabStatus){case"synced":return"Синхронизировано";case"connected":return"Подключено";case"connecting":return"Подключение...";case"disconnected":return"Соединение потеряно";default:return"Локальный режим"}},_.getStatusDescription=function(){if(_.collabPermissionDenied)return"У вас нет прав на редактирование этого доÐ
1ºÑƒÐ¼ÐµÐ½Ñ‚а. Открыто в режиме просмотра.";if(_.isOfflineMode)return"Совместная правка недоступна. Нажмите «Переподключиться»; правки пока не дошли до совместной комнаты.";if(_.isReconnecting)return"Попытка восстановить соединение с сервером...";switch(_.collabStatus){case"synced":return"Все изменения синхронизированы с сервером. Другие авторы видят ваши правки в реальном времени.";case"connected":return"Соединение установлено, идёт синхронизация документа...";case"connecting":return"Устанавливается соединение с сервером совместного редактирования...";case"disconnected":return"Связь потеряна. Повторяем подключение; правки пока не дошли до совместной комнаты.";default:return"Работа без синхронизации. Изменения сохраняются только на сервер."}},_.handleScroll=function(){D||(D=setTimeout(function(){D=null;const e=a.scrollY>0;if(!(H&&H.isConnected||(H=document.querySelector(".discours-editor.tiptap-mode"),H)))return void(_.isScrolled!==e&&t.$applyAsync(function(){_.isScrolled=e}));let n=!1;if(H){const t=H.getBoundingClientRect();n=t.bottom<0||t.top>a.innerHeight}_.isScrolled===e&&_.helpBtnHidden===n||t.$applyAsync(function(){_.isScrolled=e,_.helpBtnHidden=n})},16))}}]})},12849(){!function t(e){try{var n=document.querySelector("[ng-app]")||document.body,i=angular.element(n).injector();if(!i)return e<5?void setTimeout(function(){t(e+1)},50*Math.pow(2,e)):void 0;var s=i.get("$templateCache");s.put("/components/editorHeader/editorHeader.html",'<style> .editor-header .custom-tooltip, .editor-header .mode-tooltip, .editor-header .help-btn-tooltip, .editor-header .save-btn-tooltip, .editor-header .invite-btn-tooltip, .editor-header .panel-btn-tooltip, .editor-header .tools-btn-tooltip { opacity: 0; visibility: hidden; position: absolute; pointer-events: none; } /* Критический CSS: скрытие панели инструментов до загрузки editor.css */ .editor-header__tools-panel { opacity: 0; visibility: hidden; position: absolute; pointer-events: none; } @media (max-width: 991px), (hover: none) { .editor-header .custom-tooltip, .editor-header .mode-tooltip, .editor-header .help-btn-tooltip, .editor-header .save-btn-tooltip, .editor-header .invite-btn-tooltip, .editor-header .panel-btn-tooltip, .editor-header .tools-btn-tooltip { display: none !important; } .editor-header__content-badge, .editor-header__title { display: none !important; } } </style> <div class="editor-header" ng-class="{\'editor-header--scrolled\': $ctrl.isScrolled, \'editor-header--loading\': $ctrl.collabSyncPending }" > <div class="editor-header__container"> <div class="editor-header__logo"> <a href="/" class="logo-link"> <img class="logo-img" src="/images/logo-mini.svg" alt="Discours" loading="eager" width="26" height="28" /> </a> </div> <div class="editor-header__title" ng-if="$ctrl.docTitle"> <span class="title-text">{{ $ctrl.docTitle }}</span> <span class="custom-tooltip custom-tooltip--title">{{ $ctrl.docTitle }}</span> </div> <span class="editor-header__content-badge editor-header__content-badge--{{ $ctrl.statusClass }}" ng-if="$ctrl.statusText" >{{ $ctrl.statusText | t }}</span > <span class="editor-header__save-status" ng-if="$ctrl.showCollabControls && $ctrl.slug"> <span ng-if="$ctrl.isSaving" class="save-status save-status--saving"> <i class="fas fa-circle-notch fa-spin"></i> {{ \'Сохранение…\' | t }} </span> <span ng-if="!$ctrl.isSaving && $ctrl.isSaveSuccess" class="save-status save-status--saved"> <i class="fas fa-check"></i> {{ \'Сохранено\' | t }} </span> <span ng-if="!$ctrl.isSaving && $ctrl.isSaveError" class="save-status save-status--error"> <i class="fas fa-exclamation-triangle"></i> {{ \'Ошибка сохранения\' | t }} <span class="custom-tooltip" aria-hidden="true">{{ \'Проверьте соединение\' | t }}</span> </span> </span> <div class="editor-header__spacer"></div> <div class="editor-header__users" ng-if="$ctrl.connectedUsers && $ctrl.connectedUsers.length > 0" > <div class="user-avatar" ng-repeat="user in $ctrl.connectedUsers | limitTo:5 track by user.clientId" ng-style="{ \'border-color\': user.color }" ng-class="{\'user-avatar--no-cursor\': !$ctrl.hasCursorPosition(user) }" ng-click="$ctrl.scrollToUserCursor(user)" > <img ng-if="user.avatar" ng-src="{{ user.avatar | imageUrl: 64:64 }}" alt="{{ user.name }}" /> <span ng-if="!user.avatar" class="avatar-initial" ng-style="{ \'background\': user.color }"> {{ (user.name || \'?\').charAt(0).toUpperCase() }}
1 </span> <span class="custom-tooltip" >{{ user.name }}{{ $ctrl.hasCursorPosition(user) ? \' — \' + (\'перейти к курсору\' | t) : \'\' }}</span > </div> <div class="user-avatar user-avatar--more" ng-if="$ctrl.connectedUsers && $ctrl.connectedUsers.length > 5" > +{{ $ctrl.connectedUsers.length - 5 }} <span class="custom-tooltip">{{ $ctrl.connectedUsers.length - 5 }} {{ \'ещё\' | t }}</span> </div> </div> <button type="button" class="editor-header__panel-btn" aria-label="{{ \'Панель комментариев и правок\' | t }}" ng-attr-aria-pressed="{{ !!$ctrl.sidebarVisible }}" ng-if="$ctrl.showPanelButton" ng-class="{ active: $ctrl.sidebarVisible, \'moved-to-margin\': !$ctrl.marginViewMode }" ng-disabled="$ctrl.collabSyncPending" ng-click="$ctrl.toggleSidebar()" > <svg width="21" height="17" viewBox="0 0 21 17" fill="none" xmlns="http://www.w3.org/2000/svg" > <path d="M3.88021 0C3.03125 0 2.32031 0.151042 1.74219 0.450522C1.16667 0.75 0.731771 1.1875 0.440104 1.76302C0.145833 2.34115 0 3.04688 0 3.88021V6.52604C0 7.35938 0.145833 8.06511 0.440104 8.64063C0.734375 9.21615 1.16667 9.65625 1.74219 9.95573C2.32031 10.2552 2.95573 10.4063 3.80469 10.4063V12.4635C3.80469 12.8229 3.89323 13.1068 4.06771 13.3177C4.24479 13.5286 4.49479 13.6328 4.81771 13.6328C5.04687 13.6328 5.2526 13.5729 5.4401 13.4531C5.625 13.3307 5.85937 13.1432 6.14323 12.888L8.91406 10.4063H11.1536C12 10.4063 12.7135 10.2552 13.2891 9.95573C13.8646 9.65625 14.2995 9.21615 14.5911 8.64063C14.8854 8.06511 15.0312 7.35938 15.0312 6.52604V3.88021C15.0312 3.04427 14.8854 2.34115 14.5911 1.76302C14.2995 1.1875 13.8646 0.75 13.2891 0.450522C12.7135 0.151042 12 0 11.1536 0H3.88021ZM3.88021 1.5H11.1536C11.987 1.5 12.5938 1.69792 12.974 2.09635C13.3542 2.49219 13.5417 3.08594 13.5417 3.88021V6.52604C13.5417 7.31771 13.3542 7.91667 12.974 8.31511C12.5938 8.71615 11.987 8.91667 11.1536 8.91667H8.83333C8.58594 8.91667 8.38281 8.94011 8.22656 8.99219C8.07292 9.04427 7.91146 9.16146 7.7474 9.33854L5.20052 11.9349V9.60938C5.20052 9.35417 5.14844 9.17448 5.04167 9.07031C4.9349 8.96615 4.75521 8.91667 4.50781 8.91667H3.88021C3.04427 8.91667 2.4375 8.71615 2.0599 8.31511C1.67969 7.91667 1.48958 7.31771 1.48958 6.52604V3.88021C1.48958 3.08594 1.67969 2.49219 2.0599 2.09635C2.4375 1.69792 3.04427 1.5 3.88021 1.5ZM16.1016 3C16.1406 3.28125 16.1589 3.57292 16.1589 3.88021V4.5H16.2708C17.1068 4.5 17.7161 4.69792 18.0938 5.09635C18.4714 5.48958 18.6641 6.08594 18.6641 6.88021V9.59375C18.6641 10.388 18.4714 10.9844 18.0938 11.3802C17.7161 11.7839 17.1068 11.9818 16.2708 11.9818H15.6458C15.3984 11.9818 15.2188 12.0339 15.112 12.138C15.0052 12.2396 14.9505 12.4193 14.9505 12.6745V15.0052L12.4062 12.4089C12.2396 12.2292 12.0781 12.112 11.9245 12.0599C11.7708 12.0078 11.5677 11.9818 11.3203 11.9818C8.80729 11.9818 8.96094 11.9844 8.92188 11.9766L7.47656 13.2682C7.78646 13.362 8.27344 13.474 9 13.474H11.2344L14.0104 15.9557C14.2917 16.2109 14.5286 16.3984 14.7109 16.5182C14.8984 16.638 15.1042 16.6979 15.3333 16.6979C15.6562 16.6979 15.9089 16.5938 16.0833 16.3828C16.2604 16.1745 16.3464 15.888 16.3464 15.5339V13.474C17.1979 13.474 17.8307 13.3255 18.4089 13.0234C18.987 12.724 19.4167 12.2839 19.7135 11.7083C20.0052 11.1328 20.1536 10.4297 20.1536 9.59375V6.88021C20.1536 6.04427 20.0052 5.34115 19.7135 4.76302C19.4167 4.1875 18.987 3.75 18.4089 3.45052C17.8307 3.15104 17.1224 3 16.2708 3H16.1016ZM3.89583 3.7526C3.60156 3.7526 3.3776 3.99219 3.3776 4.27865C3.3776 4.57031 3.60156 4.80729 3.89583 4.80729H11.3281C11.6224 4.80729 11.8464 4.56771 11.8464 4.27865C11.8464 3.98958 11.6224 3.7526 11.3281 3.7526H3.89583ZM3.89583 6.01563C3.53385 6.01563 3.3776 6.33073 3.3776 6.55208C3.3776 6.84115 3.60156 7.07031 3.89583 7.07031H8.81771C9.10677 7.07031 9.33594 6.84635 9.33594 6.55208C9.33594 6.33594 9.17969 6.01563 8.81771 6.01563H3.89583Z" fill="currentColor" /> </svg> <span class="panel-counter" ng-if="$ctrl.pendingCount > 0">{{ $ctrl.pendingCount }}</span> <span class="panel-btn-tooltip">{{ \'Панель комментариев и правок\' | t }}</span> </button> <button type="button" class="editor-header__mode-toggle-btn" aria-label="{{ \'Переключить режим просмотра\' | t }}
1" ng-if="!$ctrl.showSuggestionMode" ng-click="$ctrl.togglePreviewMode()" > <span ng-if="$ctrl.currentMode !== \'preview\'">{{ \'Предпросмотр\' | t }}</span> <span ng-if="$ctrl.currentMode === \'preview\'">{{ \'Редактировать\' | t }}</span> </button> <button type="button" class="editor-header__mode-toggle-btn" aria-label="{{ \'Переключить режим просмотра\' | t }}" ng-if="$ctrl.showSuggestionMode && !$ctrl.canEdit" ng-click="$ctrl.togglePreviewMode()" > <span ng-if="$ctrl.currentMode !== \'preview\'">{{ \'Предпросмотр\' | t }}</span> <span ng-if="$ctrl.currentMode === \'preview\'">{{ \'Комментирование\' | t }}</span> </button> <div class="editor-header__mode-switcher" ng-class="{ open: $ctrl.modeSwitcherOpen }" ng-if="$ctrl.showSuggestionMode && $ctrl.canEdit" > <button type="button" class="mode-switcher-toggle" aria-label="{{ \'Выбрать режим редактора\' | t }}" aria-haspopup="true" aria-expanded="{{ $ctrl.modeSwitcherOpen }}" ng-click="$ctrl.toggleModeSwitcher()" ng-blur="$ctrl.closeModeSwitcherDelayed()" > <span class="mode-tooltip">{{ \'Режим редактора\' | t }}</span> <span class="mode-icon" ng-if="$ctrl.currentMode === \'preview\'"> <svg width="21" height="14" viewBox="0 0 21 14" fill="none" xmlns="http://www.w3.org/2000/svg" > <path d="M10.125 0C4.6875 0 0 4.52865 0 6.75C0 8.97135 4.6875 13.5 10.125 13.5C15.5625 13.5 20.25 8.97135 20.25 6.75C20.25 4.52865 15.5625 0 10.125 0ZM10.125 1.5C14.9792 1.5 18.75 5.6875 18.75 6.75C18.75 7.8125 14.9792 12 10.125 12C5.27083 12 1.5 7.8125 1.5 6.75C1.5 5.6875 5.27083 1.5 10.125 1.5ZM10.125 2.625C7.84635 2.625 6 4.47135 6 6.75C6 9.02865 7.84635 10.875 10.125 10.875C12.4036 10.875 14.25 9.02865 14.25 6.75C14.25 4.47135 12.4036 2.625 10.125 2.625ZM10.125 5.25C10.9531 5.25 11.625 5.92188 11.625 6.75C11.625 7.57813 10.9531 8.25 10.125 8.25C9.29688 8.25 8.625 7.57813 8.625 6.75C8.625 5.92188 9.29688 5.25 10.125 5.25Z" fill="currentColor" /> </svg> </span> <span class="mode-icon" ng-if="$ctrl.currentMode === \'editing\'"> <svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg" > <path d="M17.9975 4.89363L13.1038 0L0 13.1064V18H4.89363L17.9975 4.89363ZM13.105 2.55674L15.4421 4.89383L14.4612 5.87591L12.1241 3.53882L13.105 2.55674ZM10.8453 4.81648L13.1824 7.15357L5.42174 14.9137L3.08485 12.5756L10.8453 4.81648ZM1.80725 16.1915V13.8544L4.14434 16.1915H1.80725Z" fill="currentColor" /> </svg> </span> <span class="mode-icon" ng-if="$ctrl.currentMode === \'suggesting\'"> <svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg" > <mask id="mask0_toggle" style="mask-type: alpha" maskUnits="userSpaceOnUse" x="0" y="14" width="4" height="4" > <rect y="14.8242" width="3.17647" height="3.17647" fill="#C4C4C4" /> </mask> <g mask="url(#mask0_toggle)"> <path d="M16.0941 1.05859H0.847027C0.379194 1.05859 0 1.43779 0 1.90562V17.9998L3.38824 14.6115H16.0942C16.562 14.6115 16.9412 14.2323 16.9412 13.7645V1.90565C16.9412 1.43782 16.562 1.05863 16.0942 1.05863L16.0941 1.05859ZM15.2471 12.9174H1.69412V2.75271H15.2471V12.9174Z" fill="currentColor" /> </g> <rect x="1" y="1" width="16" height="12.8235" stroke="currentColor" stroke-width="2" /> <rect x="4.23438" y="3.17578" width="9.52941" height="2.11765" fill="currentColor" /> <rect x="4.23438" y="9.5293" width="7.41176" height="2.11765" fill="currentColor" /> <rect x="4.23438" y="6.35352" width="5.29412" height="2.11765" fill="currentColor" /> </svg> </span> <span class="mode-label"> <span ng-if="$ctrl.currentMode === \'preview\'">{{ \'Предпросмотр\' | t }}</span> <span ng-if="$ctrl.currentMode === \'editing\'">{{ \'Редактирование\' | t }}</span> <span ng-if="$ctrl.currentMode === \'suggesting\'">{{ \'Комментирование\' | t }}</span> </span> <i class="fas fa-chevron-down mode-chevron"></i> </button> <div class="mode-switcher-dropdown" role="menu"> <div class="mode-dropdown-content"> <button type="button" class="mode-option" role="menuitem" ng-class="{ active: $ctrl.currentMode === \'preview\' }" ng-mousedown="$event.preventDefault(); $ctrl.setMode(\'preview\')" ng-click="$ctrl.setMode(\'preview\')" > <div class="mode-option-header"> <span class="mode-option-icon"> <svg width="21" height="14" viewBox="0 0 21 14" fill="none" xmlns="http://www.w3.org/2000/svg" > <path d="M10.125 0C4.6875 0 0 4.52865 0 6.75C0 8.97135 4.6875 13.5 10.125 13.5C15.5625 13.5 20.25 8.97135 20.25 6.75C20.25 4.52865 15.5625 0 10.125 0ZM10.125 1.5C14.9792 1.5 18.75 5.687
15 18.75 6.75C18.75 7.8125 14.9792 12 10.125 12C5.27083 12 1.5 7.8125 1.5 6.75C1.5 5.6875 5.27083 1.5 10.125 1.5ZM10.125 2.625C7.84635 2.625 6 4.47135 6 6.75C6 9.02865 7.84635 10.875 10.125 10.875C12.4036 10.875 14.25 9.02865 14.25 6.75C14.25 4.47135 12.4036 2.625 10.125 2.625ZM10.125 5.25C10.9531 5.25 11.625 5.92188 11.625 6.75C11.625 7.57813 10.9531 8.25 10.125 8.25C9.29688 8.25 8.625 7.57813 8.625 6.75C8.625 5.92188 9.29688 5.25 10.125 5.25Z" fill="currentColor" /> </svg> </span> <span class="mode-option-title">{{ \'Предпросмотр\' | t }}</span> </div> <div class="mode-option-desc"> {{ \'Посмотрите как материал будет выглядеть при публикации\' | t }} </div> </button> <button type="button" class="mode-option" role="menuitem" ng-class="{ active: $ctrl.currentMode === \'editing\' }" ng-mousedown="$event.preventDefault(); $ctrl.setMode(\'editing\')" ng-click="$ctrl.setMode(\'editing\')" ng-if="$ctrl.canEdit" > <div class="mode-option-header"> <span class="mode-option-icon"> <svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg" > <path d="M17.9975 4.89363L13.1038 0L0 13.1064V18H4.89363L17.9975 4.89363ZM13.105 2.55674L15.4421 4.89383L14.4612 5.87591L12.1241 3.53882L13.105 2.55674ZM10.8453 4.81648L13.1824 7.15357L5.42174 14.9137L3.08485 12.5756L10.8453 4.81648ZM1.80725 16.1915V13.8544L4.14434 16.1915H1.80725Z" fill="currentColor" /> </svg> </span> <span class="mode-option-title">{{ \'Редактирование\' | t }}</span> </div> <div class="mode-option-desc">{{ \'Пишите и редактируйте текст напрямую\' | t }}</div> </button> <button type="button" class="mode-option" role="menuitem" ng-if="$ctrl.showSuggestionMode" ng-class="{ active: $ctrl.currentMode === \'suggesting\' }" ng-mousedown="$event.preventDefault(); $ctrl.setMode(\'suggesting\')" ng-click="$ctrl.setMode(\'suggesting\')" > <div class="mode-option-header"> <span class="mode-option-icon"> <svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg" > <mask id="mask0_dropdown" style="mask-type: alpha" maskUnits="userSpaceOnUse" x="0" y="14" width="4" height="4" > <rect y="14.8242" width="3.17647" height="3.17647" fill="#C4C4C4" /> </mask> <g mask="url(#mask0_dropdown)"> <path d="M16.0941 1.05859H0.847027C0.379194 1.05859 0 1.43779 0 1.90562V17.9998L3.38824 14.6115H16.0942C16.562 14.6115 16.9412 14.2323 16.9412 13.7645V1.90565C16.9412 1.43782 16.562 1.05863 16.0942 1.05863L16.0941 1.05859ZM15.2471 12.9174H1.69412V2.75271H15.2471V12.9174Z" fill="currentColor" /> </g> <rect x="1" y="1" width="16" height="12.8235" stroke="currentColor" stroke-width="2" /> <rect x="4.23438" y="3.17578" width="9.52941" height="2.11765" fill="currentColor" /> <rect x="4.23438" y="9.5293" width="7.41176" height="2.11765" fill="currentColor" /> <rect x="4.23438" y="6.35352" width="5.29412" height="2.11765" fill="currentColor" /> </svg> </span> <span class="mode-option-title">{{ \'Комментирование\' | t }}</span> </div> <div class="mode-option-desc"> {{ \'Предлагайте правки и комментарии, чтобы сделать текст лучше\' | t }} </div> </button> </div> </div> </div> <button type="button" class="editor-header__invite-btn" aria-label="{{ \'Пригласить соавторов\' | t }}" ng-if="$ctrl.showInviteButton && $ctrl.canEdit" ng-click="$ctrl.openInviteModal()" > <svg class="invite-btn-icon" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" > <path d="M8.667 7.333a2.667 2.667 0 100-5.333 2.667 2.667 0 000 5.333zM1.333 14c0-2.946 3.283-5.333 7.333-5.333.598 0 1.179.059 1.733.168M12 10.667v4M10 12.667h4" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" /> </svg> <span class="invite-btn-text">{{ \'Пригласить\' | t }}</span> <span class="invite-btn-tooltip">{{ \'Пригласить соавторов\' | t }}</span> </button> <button type="button" class="editor-header__save-btn" ng-if="$ctrl.canEdit && $ctrl.onSave" ng-click="$ctrl.save()" ng-disabled="$ctrl.isSavingVersion || $ctrl.isSaving || $ctrl.isPublishing || $ctrl.isSaveSuccess" ng-class="{\'save-btn--success\': $ctrl.isSaveSuccess }" aria-label="{{ \'Сохранить\' | t }}" aria-busy="{{ $ctrl.isSavingVersion }}" >
1 <span ng-if="!$ctrl.isSavingVersion && !$ctrl.isSaveSuccess">{{ \'Сохранить\' | t }}</span> <span ng-if="$ctrl.isSavingVersion"><i class="fas fa-sync-alt fa-spin"></i></span> <span ng-if="$ctrl.isSaveSuccess">{{ \'Сохранено\' | t }}</span> <span class="save-btn-tooltip" ng-if="!$ctrl.isSaveSuccess" >{{ \'Сохранить\' | t }} ({{ $ctrl.isMac ? \'⌘S\' : \'Ctrl+S\' }})</span > <span class="save-btn-tooltip" ng-if="$ctrl.isSaveSuccess" >{{ \'Сохранено в\' | t }} {{ $ctrl.getLastVersionSavedTooltip() }}</span > </button> <button type="button" class="editor-header__save-btn" ng-if="$ctrl.canEdit && !$ctrl.onSave && $ctrl.slug" ng-click="$ctrl.publish()" ng-disabled="$ctrl.isPublishing" aria-label="{{ $ctrl.publishButtonText | t }}" > <span ng-if="!$ctrl.isPublishing">{{ $ctrl.publishButtonText | t }}</span> <span ng-if="$ctrl.isPublishing"><i class="fas fa-sync-alt fa-spin"></i></span> </button> <div class="editor-header__tools-wrapper" ng-class="{ open: $ctrl.toolsPanelOpen,\'status--synced\': $ctrl.collabStatus === \'synced\', \'status--connected\': $ctrl.collabStatus === \'connected\', \'status--connecting\': $ctrl.collabStatus === \'connecting\' || $ctrl.isReconnecting, \'status--disconnected\': $ctrl.collabStatus === \'disconnected\', \'status--offline\': $ctrl.isOfflineMode || $ctrl.collabStatus === \'offline\', \'status--local\': !$ctrl.collabStatus && !$ctrl.isOfflineMode }" > <button type="button" class="editor-header__tools-btn" aria-label="{{ \'Инструменты и настройки\' | t }}" aria-haspopup="true" aria-expanded="{{ !!$ctrl.toolsPanelOpen }}" ng-click="$ctrl.toggleToolsPanel($event)" > <svg width="18" height="4" viewBox="0 0 18 4" fill="none" xmlns="http://www.w3.org/2000/svg" > <circle cx="2" cy="2" r="2" fill="currentColor" /> <circle cx="9" cy="2" r="2" class="status-dot-mid" fill="currentColor" /> <circle cx="16" cy="2" r="2" fill="currentColor" /> </svg> <span class="tools-btn-tooltip">{{ \'Инструменты\' | t }}</span> </button> <div class="editor-header__tools-panel" ng-class="{\'tools-panel--open\': $ctrl.toolsPanelOpen }" role="menu" aria-label="{{ \'Инструменты редактора\' | t }}" > <div class="tools-panel__section"> <button type="button" class="tools-panel__item" role="menuitem" ng-if="$ctrl.canEdit" ng-mousedown="$event.preventDefault()" ng-click="$ctrl.publish(); $ctrl.closeToolsPanel()" ng-disabled="$ctrl.isPublishing" > <span class="tools-panel__item-icon"> <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg" > <circle cx="10" cy="10" r="7.5" stroke="currentColor" stroke-width="1.5" /> <path d="M10 2.5c-2.5 3-2.5 9.5 0 15" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" /> <path d="M10 2.5c2.5 3 2.5 9.5 0 15" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" /> <path d="M2.5 10h15M3.5 6.5h13M3.5 13.5h13" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" /> </svg> </span> <span class="tools-panel__item-title">{{ $ctrl.toolsPublishText | t }}</span> </button> <button type="button" class="tools-panel__item" role="menuitem" ng-if="$ctrl.canEdit && $ctrl.onSave" ng-mousedown="$event.preventDefault()" ng-click="$ctrl.saveDraftInPlace()" ng-disabled="$ctrl.isSaving || $ctrl.isSaveSuccess" > <span class="tools-panel__item-icon"> <i ng-if="$ctrl.isSaving" class="fas fa-sync-alt fa-spin"></i> <svg ng-if="!$ctrl.isSaving" width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg" > <path d="M3.5 4.5A1 1 0 014.5 3.5h8.172a1 1 0 01.707.293l2.828 2.828a1 1 0 01.293.707V15.5a1 1 0 01-1 1h-11a1 1 0 01-1-1v-11z" stroke="currentColor" stroke-width="1.5" /> <path d="M7 3.5V7h5.5V3.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /> <rect x="5.5" y="11" width="9" height="5.5" rx="0.5" stroke="currentColor" stroke-width="1.5" /> </svg> </span> <span class="tools-panel__item-title">{{ \'Сохранить черновик\' | t }}</span> <span class="tools-panel__item-sub tools-panel__item-sub--success" ng-if="$ctrl.isSaveSuccess" >{{ \'Сохранено!\' | t }}</span > </button> <button type="button" class="tools-panel__item" role="menuitem" ng-if="$ctrl.showInviteButton" ng-mousedown="$event.preventDefault()" ng-click="$ctrl.openInviteModal();
1 $ctrl.closeToolsPanel()" > <span class="tools-panel__item-icon"> <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg" > <path d="M10.833 9.167a3.333 3.333 0 100-6.667 3.333 3.333 0 000 6.667zM1.667 17.5c0-3.682 4.104-6.667 9.166-6.667.748 0 1.474.074 2.167.21M15 13.333v5M12.5 15.833h5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /> </svg> </span> <span class="tools-panel__item-title">{{ \'Пригласить соавторов\' | t }}</span> <span class="tools-panel__item-badge" ng-if="$ctrl.inviteCount > 0" >{{ $ctrl.inviteCount }}</span > </button> <div class="tools-panel__divider"></div> <button type="button" class="tools-panel__item" role="menuitem" ng-if="$ctrl.canEdit && $ctrl.isLoggedIn && $ctrl.onOpenSettings" ng-mousedown="$event.preventDefault()" ng-click="$ctrl.openSettings()" > <span class="tools-panel__item-icon"> <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg" > <path d="M10 12.5a2.5 2.5 0 100-5 2.5 2.5 0 000 5z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /> <path d="M16.167 12.5a1.375 1.375 0 00.275 1.517l.05.05a1.667 1.667 0 11-2.359 2.358l-.05-.05a1.375 1.375 0 00-1.516-.275 1.375 1.375 0 00-.834 1.258v.142a1.667 1.667 0 11-3.333 0v-.075a1.375 1.375 0 00-.9-1.258 1.375 1.375 0 00-1.517.275l-.05.05a1.667 1.667 0 11-2.358-2.358l.05-.05a1.375 1.375 0 00.275-1.517 1.375 1.375 0 00-1.258-.833H2.5a1.667 1.667 0 110-3.334h.075a1.375 1.375 0 001.258-.9 1.375 1.375 0 00-.275-1.516l-.05-.05A1.667 1.667 0 115.867 3.78l.05.05a1.375 1.375 0 001.516.275h.067A1.375 1.375 0 008.333 2.85V2.5a1.667 1.667 0 113.334 0v.075a1.375 1.375 0 00.833 1.258 1.375 1.375 0 001.517-.275l.05-.05a1.667 1.667 0 112.358 2.359l-.05.05a1.375 1.375 0 00-.275 1.516v.067a1.375 1.375 0 001.258.833H17.5a1.667 1.667 0 110 3.334h-.075a1.375 1.375 0 00-1.258.833z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /> </svg> </span> <span class="tools-panel__item-title">{{ \'Настройки публикации\' | t }}</span> </button> <button type="button" class="tools-panel__item" role="menuitem" ng-if="$ctrl.canEdit && $ctrl.tiptapEditor" ng-mousedown="$event.preventDefault()" ng-click="$ctrl.openImportExport()" > <span class="tools-panel__item-icon"> <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg" > <path d="M4 14.5V16a1.5 1.5 0 001.5 1.5h9A1.5 1.5 0 0016 16v-1.5M13.5 6L10 2.5 6.5 6M10 2.5v10" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /> </svg> </span> <span class="tools-panel__item-title">{{ \'Импорт / Экспорт\' | t }}</span> </button> <div class="tools-panel__item-row" ng-if="$ctrl.slug && $ctrl.onVersionsClick"> <button type="button" class="tools-panel__item tools-panel__item--flex" role="menuitem" ng-mousedown="$event.preventDefault()" ng-click="$ctrl.onVersionsClick(); $ctrl.closeToolsPanel()" > <span class="tools-panel__item-icon"> <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg" > <circle cx="10" cy="10" r="7.5" stroke="currentColor" stroke-width="1.5" /> <path d="M10 7V10.5l2.5 1.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /> </svg> </span> <span class="tools-panel__item-title">{{ \'История версий\' | t }}</span> </button> <button type="button" class="tools-panel__save-version-btn" aria-label="{{ \'Сохранить версию\' | t }}" ng-if="$ctrl.canEdit && $ctrl.onSaveVersionDialog" ng-mousedown="$event.preventDefault()" ng-click="$ctrl.onSaveVersionDialog(); $ctrl.closeToolsPanel()" > <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" > <path d="M2 4a2 2 0 012-2h7.586a1 1 0 01.707.293l1.414 1.414A1 1 0 0114 4.414V13a1 1 0 01-1 1H3a1 1 0 01-1-1V4z" stroke="currentColor" stroke-width="1.2" /> <path d="M5 2v3h5V2M5 14v-4h6v4" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" /> </svg> </button> </div> </div> <div class="tools-panel__divider"></div> <div class="tools-panel__section"> <button type="button" class="tools-panel__item" role="menuitem" ng-if="$ctrl.onRunTypographer && $ctrl.isAdmin" ng-mousedown="$event.preventDefault()" ng-click="$ctrl.runTypographer()" > <span class="tools-panel__item-icon"> <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg" > <path d="M4 15.5h12M6.5 12l3.5-8 3.5 8M8 9.5h4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /> </svg> </span> <span class="tools-panel__item-title">{{ \'Автотипограф\' | t }}</span> <span class="tools-panel__item-sub tools-panel__item-sub--success" ng-if="$ctrl.typographerDone" >{{ \'Готово!\' | t }}</span > </button> <button type="button" class="tools-panel__item" role="menuitem" ng-if="$ctrl.canEdit && $ctrl.tiptapEditor" ng-mousedown="$event.preventDefault()" ng-click="$ctrl.toggleLanguageTool()" > <span class="tools-panel__item-icon"> <i ng-if="$ctrl.ltChecking" class="fas fa-sync-alt fa-spin" aria-hidden="true" ng-attr-aria-label="{{ \'Проверка...\' | t }}" ></i>
1 <svg ng-if="!$ctrl.ltChecking" width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg" > <path d="M3.333 5H10M3.333 8.333H10M3.333 11.667h5M13.333 10l-2.5 5 2.5-1.667 2.5 1.667-2.5-5z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /> </svg> </span> <span class="tools-panel__item-title">{{ \'Проверка грамматики\' | t }}</span> <span class="tools-panel__lt-rate-limit" ng-if="$ctrl.ltRateLimited" >⏳<span class="custom-tooltip" aria-hidden="true" >{{ \'Лимит запросов — повтор через минуту\' | t }}</span ></span > <div class="tools-panel__toggle" ng-class="{\'tools-panel__toggle--on\': $ctrl.ltEnabled }" > <div class="tools-panel__toggle-knob"></div> </div> </button> <button type="button" class="tools-panel__item tools-panel__item--margin-view" role="menuitem" ng-attr-aria-label="{{ \'Комментарии на полях\' | t }}" ng-attr-aria-pressed="{{ $ctrl.marginViewMode ? \'true\' : \'false\' }}" ng-if="$ctrl.showCollabControls" ng-mousedown="$event.preventDefault()" ng-click="$ctrl.toggleMarginViewFromTools()" > <span class="tools-panel__item-icon"> <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg" > <rect x="2" y="3.5" width="10" height="13" rx="1" stroke="currentColor" stroke-width="1.5" /> <path d="M14.5 7h3M14.5 10h3M14.5 13h2" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" /> <rect x="13" y="5.5" width="5" height="4" rx="1" stroke="currentColor" stroke-width="1.2" /> </svg> </span> <span class="tools-panel__item-title">{{ \'Комментарии на полях\' | t }}</span> <div class="tools-panel__toggle" ng-class="{\'tools-panel__toggle--on\': $ctrl.marginViewMode }" > <div class="tools-panel__toggle-knob"></div> </div> </button> <button type="button" class="tools-panel__item" role="menuitem" ng-if="$ctrl.isAdmin" ng-mousedown="$event.preventDefault()" ng-click="$ctrl.cleanHtmlContent()" > <span class="tools-panel__item-icon"> <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg" > <path d="M7.5 4.167L3.333 10 7.5 15.833M12.5 4.167l4.167 5.833-4.167 5.833" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /> </svg> </span> <span class="tools-panel__item-title">{{ \'Почистить код\' | t }}</span> <span class="tools-panel__item-sub tools-panel__item-sub--success" ng-if="$ctrl.cleanCodeDone" >{{ \'Готово!\' | t }}</span > </button> <button type="button" class="tools-panel__item" role="menuitem" ng-if="$ctrl.tiptapEditor" ng-mousedown="$event.preventDefault()" ng-click="$ctrl.openSearchReplace(); $ctrl.closeToolsPanel()" > <span class="tools-panel__item-icon"> <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg" > <path d="M8.5 3a5.5 5.5 0 1 0 3.4 9.8l3.8 3.8M13.5 12.5l-2-2M6 8h5M6 11h3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /> </svg> </span> <span class="tools-panel__item-title">{{ \'Найти и заменить\' | t }}</span> </button> <div class="tools-panel__divider"></div> <button type="button" class="tools-panel__item" role="menuitem" ng-mousedown="$event.preventDefault()" ng-click="$ctrl.openShortcutsHelp(); $ctrl.closeToolsPanel()" > <span class="tools-panel__item-icon"> <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg" > <rect x="1.667" y="4.167" width="16.667" height="11.667" rx="2" stroke="currentColor" stroke-width="1.5" /> <path d="M5 8.333h1.667M5 11.667h1.667M8.333 8.333h3.334M8.333 11.667H11.667M13.333 8.333H15M13.333 11.667H15" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" /> </svg> </span> <span class="tools-panel__item-title">{{ \'Горячие клавиши\' | t }}</span> </button> <a class="tools-panel__item" href="/articles/theory/how-to-write-a-good-article" target="_blank" rel="noopener noreferrer" role="menuitem" ng-if="false" ng-click="$ctrl.closeToolsPanel
1()" > <span class="tools-panel__item-icon"> <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg" > <path d="M10 2.5a7.5 7.5 0 100 15 7.5 7.5 0 000-15z" stroke="currentColor" stroke-width="1.5" /> <path d="M10 13.333v-5M10 6.667v-.834" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" /> </svg> </span> <span class="tools-panel__item-title">{{ \'Как написать хороший текст\' | t }}</span> </a> <button type="button" class="tools-panel__item" role="menuitem" ng-mousedown="$event.preventDefault()" ng-click="$ctrl.openHelpModal()" > <span class="tools-panel__item-icon"> <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg" > <path d="M10 2.5a7.5 7.5 0 100 15 7.5 7.5 0 000-15z" stroke="currentColor" stroke-width="1.5" /> <path d="M7.575 7.5a2.5 2.5 0 014.85.833c0 1.667-2.5 2.5-2.5 2.5M10 13.333h.008" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /> </svg> </span> <span class="tools-panel__item-title">{{ \'Помощь\' | t }}</span> </button> </div> <div class="tools-panel__divider"></div> <div class="tools-panel__section"> <button type="button" class="tools-panel__item" role="menuitem" ng-attr-aria-pressed="{{ $ctrl.quietMode ? \'true\' : \'false\' }}" ng-if="$ctrl.showCollabControls" ng-mousedown="$event.preventDefault()" ng-click="$ctrl.toggleQuietModeFromTools()" > <span class="tools-panel__item-icon"> <svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" > <path d="M17.94 17.94A10.07 10.07 0 0112 20c-7 0-11-8-11-8a18.45 18.45 0 015.06-5.94M9.9 4.24A9.12 9.12 0 0112 4c7 0 11 8 11 8a18.5 18.5 0 01-2.16 3.19m-6.72-1.07a3 3 0 11-4.24-4.24" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /> <line x1="1" y1="1" x2="23" y2="23" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" /> </svg> </span> <span class="tools-panel__item-title">{{ \'Тихий режим\' | t }}</span> <div class="tools-panel__toggle" ng-class="{\'tools-panel__toggle--on\': $ctrl.quietMode }" > <div class="tools-panel__toggle-knob"></div> </div> </button> <button type="button" class="tools-panel__item" role="menuitem" ng-attr-aria-pressed="{{ $ctrl.nightMode ? \'true\' : \'false\' }}" ng-mousedown="$event.preventDefault()" ng-click="$ctrl.toggleNightMode()" > <span class="tools-panel__item-icon"> <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg" > <path d="M17.5 10.833A7.5 7.5 0 019.167 2.5a7.5 7.5 0 000 15A7.5 7.5 0 0017.5 10.833z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /> </svg> </span> <span class="tools-panel__item-title">{{ \'Ночная тема\' | t }}</span> <div class="tools-panel__toggle" ng-class="{\'tools-panel__toggle--on\': $ctrl.nightMode }" > <div class="tools-panel__toggle-knob"></div> </div> </button> </div> <div class="tools-panel__footer"> <div class="tools-panel__stats" ng-if="$ctrl.wordCount != null || $ctrl.charCount != null || $ctrl.paraCount != null" > <span>{{ $ctrl.wordCount | pluralize:\'слово\':\'слова\':\'слов\' }}</span> <span class="tools-panel__stats-sep">&middot;</span> <span>{{ $ctrl.charCount | pluralize:\'знак\':\'знака\':\'знаков\' }}</span> <span class="tools-panel__stats-sep">&middot;</span> <span>{{ $ctrl.paraCount | pluralize:\'абзац\':\'абзаца\':\'абзацев\' }}</span> </div> <div class="tools-panel__last-modified" ng-if="$ctrl.lastModifiedFormatted"> {{ \'Последнее изменение:\' | t }} {{ $ctrl.lastModifiedFormatted }} </div> <div class="tools-panel__status-compact" ng-if="$ctrl.showCollabStatus" ng-class="{\'status--synced\': $ctrl.collabStatus === \'synced\', \'status--connected\': $ctrl.collabStatus === \'connected\', \'status--connecting\': $ctrl.collabStatus === \'connecting\' || $ctrl.isReconnecting, \'status--disconnected\': $ctrl.collabStatus === \'disconnected\', \'status--offline\': $ctrl.isOfflineMode || $ctrl.collabStatus === \'offline\' }" > <span class="tools-panel__status-dot-small"></span> <span class="tools-panel__status-compact-text">{{ $ctrl._statusTitleCache | t }}</span> <span class="tools-panel__status-hint" ng-if="$ctrl.statusDescription" ng-bind="$ctrl.statusDescription | t" ></span> <button type="button" class="tools-panel__status-compact-reconnect" ng-if="$ctrl.isOfflineMode || $ctrl.collabStatus === \'disconnected\'" ng-mousedown="$event.preventDefault()" ng-click="$ctrl.retryConnection()" ng-disabled="$ctrl.isReconnecting" >
1 <i ng-if="!$ctrl.isReconnecting" class="fas fa-redo"></i> <i ng-if="$ctrl.isReconnecting" class="fas fa-sync-alt fa-spin"></i> <span ng-if="!$ctrl.isReconnecting">{{ \'Переподключиться\' | t }}</span> </button> </div> </div> </div> </div> <div class="editor-header__avatar" ng-if="!$ctrl.isLoggedIn"> <button type="button" class="avatar-btn" ng-click="$ctrl.openLogin()" aria-label="{{ \'Войти в аккаунт\' | t }}" > <span class="custom-tooltip">{{ \'Войти в аккаунт\' | t }}</span> <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" > <path d="M8 8a3 3 0 100-6 3 3 0 000 6zM2 14c0-2.21 2.686-4 6-4s6 1.79 6 4" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" /> </svg> </button> </div> <div class="editor-header__avatar" ng-if="$ctrl.isLoggedIn"> <button type="button" class="avatar-btn avatar-btn--logged-in" ng-click="$ctrl.openProfileMenu()" aria-label="{{ \'Профиль пользователя\' | t }}" aria-haspopup="true" aria-expanded="{{ !!$ctrl.profileMenuOpen }}" > <img ng-if="$ctrl.userAvatar && !$ctrl.avatarLoadError" ng-src="{{ $ctrl.userAvatar | imageUrl: 64:64 }}" alt="{{ \'Аватар\' | t }}" class="avatar-img" ng-img-error="$ctrl.avatarLoadError = true" /> <svg ng-if="!$ctrl.userAvatar || $ctrl.avatarLoadError" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" > <path d="M8 8a3 3 0 100-6 3 3 0 000 6zM2 14c0-2.21 2.686-4 6-4s6 1.79 6 4" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" /> </svg> </button> <div class="editor-header__profile-menu" ng-class="{ open: $ctrl.profileMenuOpen }" role="menu" aria-label="{{ \'Меню профиля\' | t }}" > <a ng-href="/{{ $ctrl.userPath || \'\' }}" class="profile-menu__item" role="menuitem"> <svg viewBox="0 0 15 16" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" > <path d="M12.73 15.75H1.72C.53 15.75-.31 14.56.1 13.45 1.08 10.82 3.94 9.05 7.23 9.05c3.28 0 6.14 1.77 7.12 4.4.41 1.11-.43 2.3-1.62 2.3zM7.23 10.55c-2.62 0-4.97 1.41-5.72 3.42-.03.08.02.28.21.28h11.01c.21 0 .24-.2.21-.28-.75-2.01-3.1-3.42-5.71-3.42z" fill="currentColor" /> <path d="M7.23 7.88c-2.07 0-3.75-1.77-3.75-3.94C3.48 1.77 5.16 0 7.23 0c2.07 0 3.75 1.77 3.75 3.94 0 2.17-1.68 3.94-3.75 3.94zm0-6.38c-1.24 0-2.25 1.1-2.25 2.44s1.01 2.44 2.25 2.44 2.25-1.1 2.25-2.44S8.47 1.5 7.23 1.5z" fill="currentColor" /> </svg> {{ \'Профиль\' | t }} </a> <a ng-href="/drafts" class="profile-menu__item" role="menuitem"> <svg viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" > <path d="M13.44 0c-.61 0-1.22.23-1.69.7L1.32 11.13.03 14.96c-.17.51.35 1 .86.83l3.83-1.28L15.13 4.07c.93-.93.93-2.44 0-3.38C14.66.23 14.05 0 13.44 0zm-.06 1.56c.23 0 .45.09.63.26.34.35.34.91 0 1.25l-.52.52-1.25-1.25.52-.52c.17-.17.4-.26.62-.26zm-2.2 1.83l1.25 1.25-7.42 7.42-1.26-1.25 7.43-7.42zM2.69 11.88l1.25 1.26-1.51.72c-.13.06-.29.04-.39-.06-.11-.1-.14-.26-.08-.4l.73-1.52z" fill="currentColor" /> </svg> {{ \'Черновики\' | t }} </a> <a ng-href="/profile/{{ $ctrl.userPath || \'\' }}?subscriptions" class="profile-menu__item" role="menuitem" > <svg width="19" height="19" viewBox="0 0 17 15" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" > <path d="M4.88 0C3.84 0 3 .84 3 1.88V3.75H1.88C.84 3.75 0 4.59 0 5.63v7.12C0 13.99 1.01 15 2.25 15h12c1.24 0 2.25-1.01 2.25-2.25V1.88C16.5.84 15.66 0 14.63 0H4.88zm0 1.5h9.75c.2 0 .37.17.37.38v10.87c0 .41-.34.75-.75.75H4.37c.08-.23.13-.49.13-.75V1.88c0-.21.17-.38.38-.38zM6.38 3c-.41 0-.75.34-.75.75s.34.75.75.75h6.75c.41 0 .75-.34.75-.75S13.54 3 13.13 3H6.38zM1.88 5.25H3v7.5c0 .41-.34.75-.75.75s-.75-.34-.75-.75V5.63c0-.21.17-.38.38-.38zm4.5.38c-.41 0-.75.34-.75.75s.34.75.75.75h6.75c.41 0 .75-.34.75-.75s-.34-.75-.75-.75H6.38zm0 2.62c-.41 0-.75.34-.75.75v2.63c0 .41.34.75.75.75h2.25c.41 0 .75-.34.75-.75V9c0-.41-.34-.75-.75-.75H6.38zm4.87 0c-.41 0-.75.34-.75.75s.34.75.75.75h1.88c.41 0 .75-.34.75-.75s-.34-.75-.75-.75H11.25zm0 2.63c-.41 0-.75.34-.75.75s.34.75.75.75h1.88c.41 0 .75-.34.75-.75s-.34-.75-.75-.75H11.25z" fill="currentColor" /> </svg> {{ \'Подписки\' | t }}
1 </a> <a ng-href="/notifications" class="profile-menu__item" role="menuitem"> <svg viewBox="0 0 21 17" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" > <path d="M3.88 0C3.03 0 2.32.15 1.74.45 1.17.75.73 1.19.44 1.76.15 2.34 0 3.05 0 3.88v2.65c0 .83.15 1.54.44 2.11.29.58.73 1.02 1.3 1.32.58.3 1.22.45 2.06.45v2.06c0 .36.09.64.26.85.18.21.43.32.75.32.23 0 .44-.06.62-.18.19-.12.42-.31.7-.56l2.78-2.48h2.24c.85 0 1.56-.15 2.14-.45.57-.3 1.01-.74 1.3-1.32.29-.57.44-1.28.44-2.11V3.88c0-.84-.15-1.54-.44-2.12-.29-.57-.73-1.01-1.3-1.31C12.71.15 12 0 11.15 0H3.88zm0 1.5h7.27c.83 0 1.44.2 1.82.6.38.4.57.99.57 1.78v2.65c0 .79-.2 1.39-.57 1.79-.38.4-.99.6-1.82.6H8.83c-.25 0-.45.02-.6.07-.16.06-.32.17-.48.35l-2.55 2.6V9.6c0-.26-.05-.43-.16-.54-.1-.1-.28-.16-.53-.16h-.63c-.84 0-1.44-.2-1.82-.6-.38-.4-.57-.99-.57-1.79V3.88c0-.79.19-1.39.57-1.78.38-.4.99-.6 1.82-.6zM16.1 3c.04.28.06.57.06.88V4.5h.11c.84 0 1.45.2 1.82.6.38.39.57.99.57 1.78v2.71c0 .8-.19 1.39-.57 1.79-.38.4-.99.6-1.82.6h-.63c-.25 0-.42.05-.53.16-.11.1-.16.29-.16.54v2.33l-2.54-2.6c-.17-.18-.33-.3-.48-.35-.16-.05-.36-.08-.6-.08-2.52 0-2.36 0-2.4-.01l-1.45 1.3c.31.09.8.2 1.53.2h2.23l2.78 2.48c.28.25.52.44.7.56.19.12.4.18.63.18.32 0 .57-.1.75-.31.18-.21.26-.5.26-.85v-2.06c.85 0 1.48-.15 2.06-.45.58-.3 1.01-.74 1.3-1.32.29-.57.44-1.28.44-2.11V6.88c0-.84-.15-1.54-.44-2.12-.3-.57-.73-1.01-1.3-1.31-.58-.3-1.29-.45-2.14-.45H16.1z" fill="currentColor" /> </svg> {{ \'Комментарии\' | t }} </a> <a ng-href="/profile/{{ $ctrl.userPath || \'\' }}" class="profile-menu__item" role="menuitem"> <svg viewBox="0 0 17 17" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" > <path d="M7.15 0c-.5 0-.93.36-1.02.86L5.91 2.13c-.11.04-.23.09-.34.14L4.52 1.52c-.46-.32-1.01-.2-1.32.1L1.64 3.2c-.36.35-.4.91-.11 1.32l.74 1.05c-.05.11-.1.22-.14.34L.86 6.13C.36 6.21 0 6.64 0 7.15v2.21c0 .5.36.93.86 1.01l1.26.22c.04.11.09.23.14.34l-.74 1.05c-.29.41-.24.97.1 1.32l1.56 1.56c.41.4.97.37 1.32.11l1.05-.74c.11.05.23.1.34.14l.22 1.27c.08.49.51.85 1.02.85h2.21c.5 0 .93-.36 1.02-.86l.22-1.27c.11-.04.23-.09.34-.14l1.05.74c.31.23.9.31 1.32-.11l1.56-1.56c.36-.36.4-.91.11-1.32l-.74-1.05c.05-.11.1-.23.14-.34l1.27-.22c.49-.08.86-.51.86-1.02V7.15c0-.5-.36-.93-.86-1.02l-1.27-.22c-.04-.11-.09-.23-.14-.34l.74-1.05c.29-.41.24-.97-.11-1.32l-1.56-1.56c-.4-.39-.95-.38-1.32-.11l-1.05.74c-.11-.05-.23-.1-.34-.14L10.37.86C10.29.36 9.86 0 9.35 0H7.15zm.43 1.5h1.34c.02 0 .04.02.05.04l.29 1.68c.66.24 1.28.49 1.84.76l1.39-.98c.02-.01.04-.01.06.01l.95.95c.02.02.02.04.01.06l-.98 1.39c.28.6.54 1.22.76 1.84l1.68.29c.02 0 .04.02.04.05v1.34c0 .02-.02.04-.04.05l-1.68.29c-.22.63-.47 1.24-.76 1.84l.98 1.39c.01.02.01.04-.01.06l-.95.95c-.02.02-.04.02-.06.01l-1.39-.98c-.61.29-1.23.54-1.84.76l-.29 1.68c0 .03-.02.04-.05.04H7.58c-.02 0-.04-.02-.05-.04l-.29-1.68c-.6-.21-1.21-.46-1.84-.76l-1.39.98c-.02.01-.04.01-.06-.01l-.95-.95c-.02-.02-.02-.04-.01-.06l.98-1.39c-.29-.63-.55-1.24-.76-1.84l-1.68-.29C1.52 8.96 1.5 8.94 1.5 8.92V7.58c0-.02.02-.04.04-.05l1.68-.29c.22-.61.47-1.22.76-1.84l-.98-1.39c-.01-.02-.01-.04.01-.06l.95-.95c.02-.02.04-.02.06-.01l1.39.98c.58-.28 1.2-.53 1.84-.76l.29-1.68c0-.02.02-.04.05-.04zM8.25 4.88c-1.86 0-3.38 1.51-3.38 3.37 0 1.86 1.52 3.38 3.38 3.38s3.38-1.52 3.38-3.38c0-1.86-1.52-3.37-3.38-3.37zm0 1.5c1.04 0 1.88.84 1.88 1.87 0 1.04-.84 1.88-1.88 1.88s-1.88-.84-1.88-1.88c0-1.03.84-1.87 1.88-1.87z" fill="currentColor" /> </svg> {{ \'Настройки\' | t }} </a> <div class="profile-menu__separator" role="separator"></div> <button type="button" ng-click="$ctrl.logOut()" class="profile-menu__item profile-menu__item--logout" role="menuitem" > <svg viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" > <path d="M12.93 0c-1.04 0-1.88.84-1.88 1.88s.84 1.87 1.88 1.87 1.87-.84 1.87-1.87S13.96 0 12.93 0zm-1.5 4.5c-.27 0-.53.06-.77.17L7.67 6.08c-.15.07-.26.18-.33.33L5.87 9.68c-.04.08-.07.16-.07.26 0 .31.25.56.56.56.23 0 .44-.14.52-.34v0l1.57-2.68.78-.28-.74 2.43.01 0c-.04.16-.08.33-.08.5 0 .71.41 1.33.99 1.65l0 0 2.92 2.13 1 3.56h0c.1.31.38.53.72.53.41 0 .75-.34.75-.75 0-.08-.02-.15-.04-.22h0l-.75-3.76c-.02-.12-.07-.24-.14-.35l-1.65-2.71 1-3.29-.02-.01c.06-.18.1-.36.1-.56 0-1.03-.84-1.88-1.88-1.88zm2.49 2.98l-.57 1.54.44.32c.04.03.07.05.12.07l2.73 1.04c.01.01.02.01.03.01l.02.01c.06.02.12.03.18.03.31 0 .56-.25.56-.56 0-.22-.13-.41-.32-.5l-2.44-1.28-.75-.68zM5.66 10.88c-.22-.02-.44.09-.55.29l-.93 1.76-1.33-.71c-.36-.19-.82-.06-1.01.31L.09 15.85c-.19.36-.05.82.32 1.01l.83.45c-.01-.26.05-.52.18-.75.26-.49.77-.8 1.33-.8.24 0 .48.06.7.18.11.06.22.13.31.22l.99-1.86 0 0 .23-.44.21-.39v0l.93-1.76c.15-.28.04-.61-.23-.76-.07-.04-.14-.05-.21-.06zm2.94 1.42l-.36 1.67-1.57 2.86-.03.06c-.06.11-.1.23-.1.36 0 .41.34.75.75.75.27 0 .51-.15.64-.37l2.54-3.96-1.87-1.37zm-5.8 4.21c-.29-.02-.58.13-.73.4-.2.37-.05.82.32 1.02.36.19.82.06 1.01-.31.19-.37.06-.82-.31-1.02-.09-.05-.18-.07-.29-.09z" fill="currentColor" /> </svg> {{ \'Выйти\' | t }} </button> </div> </div> </div> <button type="button" class="editor-header__help-btn" ng-class="{\'help-btn--hidden\': $ctrl.helpBtnHidden }" aria-label="{{ \'Горячие клавиши\' | t }}" ng-click="$ctrl.openShortcutsHelp()" > <span class="help-btn-text">?</span> <span class="help-btn-dot" ng-if="$ctrl.showHelpDot"></span> <span class="help-btn-tooltip">{{ \'Горячие клавиши\' | t }}</span> </button> </div>'),s.put("/components/inviteCollaborators/inviteCollaborators.html",'<div ng-if="$ctrl.opened" class="invite-collab-backdrop active" ng-click="$ctrl.onBackdropClick()" ></div> <div ng-if="$ctrl.opened" class="invite-collab active" ng-class="{\'invite-collab--dropdown-open\': $ctrl.accessDropdownOpen || $ctrl.linkRoleDropdownOpen || $ctrl.inputRoleDropdownOpen || $ctrl.openRoleDropdownId }" role="dialog" aria-modal="true" aria-labelledby="invite-collab-title" ng-click="$ctrl.closeAllDropdowns()" > <button type="button" class="invite-collab__close" aria-label="{{ \'Закрыть\' | t }}" ng-click="$ctrl.close()" > <svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg" > <path d="M15 5L5 15M5 5L15 15" stroke="#141414" stroke-width="2" stroke-linecap="round" /> </svg> </button> <h2 class="invite-collab__title" id="invite-collab-title">{{ \'Пригласить соавторов\' | t }}</h2> <div class="invite-collab__search-row"> <div class="invite-collab__input-wrap"> <input type="text" class="invite-collab__input" placeholder="{{ $ctrl.searchPlaceholder }}" aria-label="{{ \'Поиск пользователей по имени или email\' | t }}" ng-model="$ctrl.searchQuery" ng-change="$ctrl.onSearchInput()" ng-keydown="$ctrl.onSearchKeydown($event)" autocomplete="off" /> <div class="invite-collab__input-line"></div> <div class="invite-collab__role-selector" role="button" tabindex="0" aria-haspopup="true" ng-attr-aria-expanded="{{ $ctrl.inputRoleDropdownOpen ? \'true\' : \'false\' }}" ng-click="$ctrl.toggleInputRoleDropdown($event)" ng-keydown="$ctrl.onPseudoButtonKeydown($event, $ctrl.toggleInputRoleDropdown)" > <span class="invite-collab__role-label">
1{{ $ctrl.getRoleLabel($ctrl.selectedRole) }}</span> <svg aria-hidden="true" class="invite-collab__chevron" width="16" height="16" viewBox="0 0 16 16" fill="none" ng-class="{\'invite-collab__chevron--up\': $ctrl.inputRoleDropdownOpen }" > <path d="M4 6L8 10L12 6" stroke="#141414" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /> </svg> <div class="invite-collab__role-dropdown" ng-if="$ctrl.inputRoleDropdownOpen" ng-click="$event.stopPropagation()" > <div class="invite-collab__role-option" ng-repeat="role in $ctrl.roles track by role.id" role="button" tabindex="0" ng-class="{\'invite-collab__role-option--active\': role.id === $ctrl.selectedRole }" ng-click="$ctrl.selectInputRole(role.id); $event.stopPropagation()" ng-keydown="$ctrl.onPseudoButtonKeydown($event, $ctrl.selectInputRole, role.id)" > <div class="invite-collab__role-option-title">{{ role.label }}</div> <div class="invite-collab__role-option-desc">{{ role.description }}</div> </div> </div> </div> </div> <button type="button" class="invite-collab__add-btn" ng-click="$ctrl.addByInput()" ng-disabled="!$ctrl.searchQuery.trim()" > {{ \'Добавить\' | t }} </button> </div> <div class="invite-collab__search-dropdown" aria-live="polite" ng-if="$ctrl.searchResults.length > 0 || ($ctrl.isSearching && $ctrl.searchQuery.length >= 2)" > <div class="invite-collab__search-loading" ng-if="$ctrl.isSearching"> <i class="fas fa-sync-alt fa-spin"></i> {{ \'Поиск...\' | t }} </div> <div class="invite-collab__search-item" ng-repeat="user in $ctrl.searchResults track by user._id" role="button" tabindex="0" ng-class="{\'invite-collab__search-item--highlighted\': $index === $ctrl.highlightedIndex }" ng-click="$ctrl.selectUser(user)" ng-keydown="$ctrl.onPseudoButtonKeydown($event, $ctrl.selectUser, user)" > <div class="invite-collab__avatar invite-collab__avatar--sm"> <img ng-if="$ctrl.getAvatarUrl(user.thumborId)" ng-src="{{ $ctrl.getAvatarUrl(user.thumborId) }}" alt="" loading="lazy" width="24" height="24" /> <span ng-if="!$ctrl.getAvatarUrl(user.thumborId)" class="invite-collab__avatar-initials"> {{ $ctrl.getInitials(user.name) }} </span> </div> <div class="invite-collab__search-item-info"> <div class="invite-collab__search-item-name">{{ user.name }}</div> <div class="invite-collab__search-item-email" ng-if="user.email">{{ user.email }}</div> </div> </div> <div class="invite-collab__search-empty" ng-if="!$ctrl.isSearching && $ctrl.searchResults.length === 0 && $ctrl.searchQuery.length >= 2" > <span ng-if="!$ctrl.isEmailQuery()" >{{ \'Пользователь не найден. Введите email для приглашения.\' | t }}</span > <span ng-if="$ctrl.isEmailQuery()"> {{ \'Отправить приглашение на\' | t }} <strong>{{ $ctrl.searchQuery }}</strong>? <button type="button" class="invite-collab__search-invite-btn" ng-click="$ctrl.addByInput()" > {{ \'Пригласить\' | t }} </button> </span> </div> </div> <div class="invite-collab__list" ng-if="$ctrl.invites.length > 0"> <div class="invite-collab__invite-row" ng-repeat="invite in $ctrl.invites track by invite._id"> <div class="invite-collab__avatar"> <img ng-if="$ctrl.getAvatarUrl(invite.inviteeThumbor)" ng-src="{{ $ctrl.getAvatarUrl(invite.inviteeThumbor) }}" alt="" loading="lazy" width="32" height="32" /> <span ng-if="!$ctrl.getAvatarUrl(invite.inviteeThumbor)" class="invite-collab__avatar-initials" > {{ $ctrl.getInitials(invite.inviteeName) }} </span> </div> <div class="invite-collab__invite-info"> <div class="invite-collab__invite-name">{{ invite.inviteeName }}</div> <div class="invite-collab__invite-status invite-collab__invite-status--draft" ng-if="invite._pending" > {{ \'Будет приглашен(а)\' | t }} </div> <div class="invite-collab__invite-status-row" ng-if="!invite._pending"> <div class="invite-collab__invite-status" ng-class="\'invite-collab__invite-status--\' + $ctrl.getStatusColor(invite.status, invite)" > {{ $ctrl.getStatusLabel(invite.status, invite) }} </div> <button type="button" class="invite-collab__resend-btn" ng-if="$ctrl.canResend(invite)" ng-click="$ctrl.resendInvite(invite); $event.stopPropagation()" ng-disabled="invite._resending" > <i class="fas" ng-class="invite._resending ?\'fa-sync-alt fa-spin\' : \'fa-redo-alt\'" aria-hidden="true" ></i> {{ (invite._resending ? \'Отправка...\' : \'Отправить повторно\') | t }} </button> </div> </div> <div class="invite-collab__invite-role" ng-click="$event.stopPropagation()"> <button type="button" class="invite-collab__invite-role-btn" aria-haspopup="true" ng-attr-aria-expanded="{{ $ctrl.openRoleDropdownId === invite._id ? \'true\' : \'false\' }}" ng-click="$ctrl.toggleRoleDropdown(invite._id, $event)" > <span>{{ $ctrl.getRoleLabel(invite.role) }}</span> <svg aria-hidden="true" class="invite-collab__chevron" width="16" height="16" viewB
1ox="0 0 16 16" fill="none" ng-class="{\'invite-collab__chevron--up\': $ctrl.openRoleDropdownId === invite._id }" > <path d="M4 6L8 10L12 6" stroke="#141414" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /> </svg> </button> <div class="invite-collab__role-dropdown invite-collab__role-dropdown--invite" ng-if="$ctrl.openRoleDropdownId === invite._id" > <div class="invite-collab__role-option" ng-repeat="role in $ctrl.roles track by role.id" role="button" tabindex="0" ng-class="{\'invite-collab__role-option--active\': role.id === invite.role }" ng-click="$ctrl.updateInviteRole(invite, role.id); $event.stopPropagation()" ng-keydown="$ctrl.onPseudoButtonKeydown($event, $ctrl.updateInviteRole, invite, role.id)" > <div class="invite-collab__role-option-title">{{ role.label }}</div> <div class="invite-collab__role-option-desc">{{ role.description }}</div> </div> <div class="invite-collab__role-option invite-collab__role-option--remove" ng-click="$ctrl.removeInvite(invite); $event.stopPropagation()" role="button" tabindex="0" ng-keydown="$ctrl.onPseudoButtonKeydown($event, $ctrl.removeInvite, invite)" > <div class="invite-collab__role-option-title invite-collab__role-option-title--remove"> {{ \'Удалить\' | t }} </div> </div> </div> </div> </div> </div> <div class="invite-collab__empty" ng-if="$ctrl.loadError && !$ctrl.isLoading"> <p>{{ \'Не удалось загрузить приглашения.\' | t }}</p> <p> <button type="button" class="invite-collab__search-invite-btn" ng-click="$ctrl.loadInvites()"> {{ \'Попробовать снова\' | t }} </button> </p> </div> <div class="invite-collab__empty" ng-if="!$ctrl.isLoading && !$ctrl.loadError && $ctrl.invites.length === 0" > <p>{{ \'Пока нет приглашённых соавторов.\' | t }}</p> <p>{{ \'Найдите пользователя по имени или пригласите по email.\' | t }}</p> </div> <div class="invite-collab__loading" ng-if="$ctrl.isLoading"> <i class="fas fa-sync-alt fa-spin"></i> </div> <div class="invite-collab__general-access" ng-if="!$ctrl.isLoading && !$ctrl.loadError"> <div class="invite-collab__general-access-row"> <div class="invite-collab__avatar invite-collab__avatar--icon"> <svg aria-hidden="true" ng-if="$ctrl.linkSharing.access === \'restricted\'" width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg" > <rect x="3" y="9" width="14" height="9" rx="2" stroke="#141414" stroke-width="1.5" /> <path d="M6 9V6C6 3.79086 7.79086 2 10 2C12.2091 2 14 3.79086 14 6V9" stroke="#141414" stroke-width="1.5" stroke-linecap="round" /> </svg> <svg aria-hidden="true" ng-if="$ctrl.linkSharing.access === \'authenticated\'" width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" > <path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2" stroke="#141414" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /> <circle cx="9" cy="7" r="4" stroke="#141414" stroke-width="1.5" /> <path d="M23 21v-2a4 4 0 0 0-3-3.87" stroke="#141414" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /> <path d="M16 3.13a4 4 0 0 1 0 7.75" stroke="#141414" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /> </svg> <span ng-if="$ctrl.linkSharing.access === \'anyone\'" class="invite-collab__avatar-icon-inner" ng-include="\'/partials/icons/globe.html\'" ></span> </div> <span class="invite-collab__general-access-title">{{ \'Общий доступ\' | t }}</span> <div class="invite-collab__general-access-select" role="button" tabindex="0" aria-haspopup="true" ng-attr-aria-expanded="{{ $ctrl.accessDropdownOpen ? \'true\' : \'false\' }}" ng-click="$ctrl.toggleAccessDropdown($event)" ng-keydown="$ctrl.onPseudoButtonKeydown($event, $ctrl.toggleAccessDropdown)" > <span class="invite-collab__general-access-level" >{{ $ctrl.getAccessLabel($ctrl.linkSharing.access) }}</span > <svg aria-hidden="true" class="invite-collab__chevron" width="16" height="16" viewBox="0 0 16 16" fill="none" ng-class="{\'invite-collab__chevron--up\': $ctrl.accessDropdownOpen }" > <path d="M4 6L8 10L12 6" stroke="#141414" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /> </svg> <div class="invite-collab__access-dropdown" ng-if="$ctrl.accessDropdownOpen" ng-click="$event.stopPropagation()" > <div class="invite-collab__role-option" ng-repeat="opt in $ctrl.accessOptions track by opt.id" role="button" tabindex="0" ng-class="{\'invite-collab__role-option--active\': opt.id === $ctrl.linkSharing.access }" ng-click="$ctrl.selectAccessLevel(opt.id); $event.stopPropagation()" ng-keydown="$ctrl.onPseudoButtonKeydown($event, $ctrl.selectAccessLevel, opt.id)" > <div class="invite-collab__role-option-title">{{ opt.label }}</div> <div class="invite-collab__role-option-desc">{{ opt.description }}</div> </div> </div> </div> <span class="invite-collab__general-access-spacer"></span> <div class="invite-collab__invite-role" ng-if="$ctrl.linkSharing.access !== \'restricted\'" ng-click="$event.stopPropagation()" > <button type="button" class="invite-collab__invite-role-btn" aria-haspopup="true" ng-attr-aria-expanded="{{ $ctrl.linkRoleDropdownOpen ? \'true\' : \'false\' }}" ng-click="$ctrl.toggleLinkRoleDropdown($event)" > <span>{{ $ctrl.getRoleLabel($ctrl.linkSharing.role) }}</span> <svg aria-hidden="true" class="invite-collab__chevron" width="16" height="16" viewB
1ox="0 0 16 16" fill="none" ng-class="{\'invite-collab__chevron--up\': $ctrl.linkRoleDropdownOpen }" > <path d="M4 6L8 10L12 6" stroke="#141414" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /> </svg> </button> <div class="invite-collab__role-dropdown invite-collab__role-dropdown--invite" ng-if="$ctrl.linkRoleDropdownOpen" ng-click="$event.stopPropagation()" > <div class="invite-collab__role-option" ng-repeat="role in $ctrl.roles track by role.id" role="button" tabindex="0" ng-class="{\'invite-collab__role-option--active\': role.id === $ctrl.linkSharing.role }" ng-click="$ctrl.selectLinkRole(role.id); $event.stopPropagation()" ng-keydown="$ctrl.onPseudoButtonKeydown($event, $ctrl.selectLinkRole, role.id)" > <div class="invite-collab__role-option-title">{{ role.label }}</div> <div class="invite-collab__role-option-desc">{{ role.description }}</div> </div> </div> </div> </div> <button type="button" class="invite-collab__copy-link-btn" aria-label="{{ \'Копировать ссылку для общего доступа\' | t }}" ng-if="$ctrl.linkSharing.access !== \'restricted\'" ng-click="$ctrl.copyShareLink()" > <svg aria-hidden="true" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" > <path d="M6.5 9.5L9.5 6.5M7 11L5.5 12.5C4.39543 13.6046 2.60457 13.6046 1.5 12.5C0.395431 11.3954 0.395431 9.60457 1.5 8.5L3 7M9 5L10.5 3.5C11.6046 2.39543 13.3954 2.39543 14.5 3.5C15.6046 4.60457 15.6046 6.39543 14.5 7.5L13 9" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /> </svg> <span>{{ ($ctrl.linkCopied ? \'Ссылка скопирована!\' : \'Копировать ссылку\') | t }}</span> </button> </div> <button type="button" class="invite-collab__done-btn" ng-class="{\'invite-collab__done-btn--saved\': $ctrl.saved }" ng-click="$ctrl.done()" ng-disabled="$ctrl.isSaving" > <span ng-if="!$ctrl.isSaving && !$ctrl.saved">{{ \'Готово\' | t }}</span> <span ng-if="!$ctrl.isSaving && $ctrl.saved">{{ \'Сохранено\' | t }}</span> <span ng-if="$ctrl.isSaving" ><i class="fas fa-sync-alt fa-spin"></i> {{ \'Сохранение...\' | t }}</span > </button> </div>'),s.put("/pages/edit/author.html",'<update-meta title="{{ \'Настройки\' | t }}"></update-meta> <script type="text/ng-template" id="apple-icon-svg"> <svg viewBox="0 0 384 512" width="14" height="14" fill="currentColor"><path d="M318.7 268.7c-.2-36.7 16.4-64.4 50-84.8-18.8-26.9-47.2-41.7-84.7-44.6-35.5-2.8-74.3 20.7-88.5 20.7-15 0-49.4-19.7-76.4-19.7C63.3 141.2 4 184.8 4 273.5c0 26.2 4.8 53.3 14.4 81.2 12.8 36.7 59 126.7 107.2 125.2 25.2-.6 43-17.9 75.8-17.9 31.8 0 48.3 17.9 76.4 17.9 48.6-.7 90.4-82.5 102.6-119.3-65.2-30.7-61.7-90-61.7-91.9zm-56.6-164.2c27.3-32.4 24.8-61.9 24-72.5-24.1 1.4-52 16.4-67.9 34.9-17.5 19.8-27.8 44.3-25.6 71.9 26.1 2 49.9-11.4 69.5-34.3z"/></svg> <\/script> <div class="settings-page" ng-if="$ctrl.authorized"> <div class="ds-container"> <div class="tw:row-flex"> <aside class="tw:gutter-half tw:w-full tw:md:w-2/12 settings-sidebar"> <div class="settings-sidebar__title">{{ \'Настройки\' | t }}</div> <nav class="settings-sidebar__nav" role="tablist" aria-label="{{ \'Настройки\' | t }}"> <a class="settings-sidebar__link" href="" id="tab-about" role="tab" aria-selected="{{ $ctrl.activeTab === \'about\' }}" aria-controls="panel-about" tabindex="{{ $ctrl.activeTab === \'about\' ? \'0\' : \'-1\' }}" ng-class="{\'settings-sidebar__link--active\': $ctrl.activeTab === \'about\' }" ng-click="$ctrl.setTab(\'about\'); $event.preventDefault()" ng-keydown="$ctrl.onTabKeydown($event)" >{{ \'Профиль\' | t }}</a > <a class="settings-sidebar__link" href="" id="tab-subscriptions" role="tab" aria-selected="{{ $ctrl.activeTab === \'subscriptions\' }}" aria-controls="panel-subscriptions" tabindex="{{ $ctrl.activeTab === \'subscriptions\' ? \'0\' : \'-1\' }}" ng-class="{\'settings-sidebar__link--active\': $ctrl.activeTab === \'subscriptions\' }" ng-click="$ctrl.setTab(\'subscriptions\'); $event.preventDefault()" ng-keydown="$ctrl.onTabKeydown($event)" >{{ \'Подписки\' | t }}</a > <a class="settings-sidebar__link" href="" id="tab-notifications" role="tab" aria-selected="{{ $ctrl.activeTab === \'notifications\' }}" aria-controls="panel-notifications" tabindex="{{ $ctrl.activeTab === \'notifications\' ? \'0\' : \'-1\' }}" ng-class="{\'settings-sidebar__link--active\': $ctrl.activeTab === \'notifications\' }" ng-click="$ctrl.setTab(\'notifications\'); $event.preventDefault()" ng-keydown="$ctrl.onTabKeydown($event)" >{{ \'Уведомления\' | t }}</a > <a class="settings-sidebar__link" href="" id="tab-security" role="tab" aria-selected="{{ $ctrl.activeTab === \'security\' }}" aria-controls="panel-security" tabindex="{{ $ctrl.activeTab === \'security\' ? \'0\' : \'-1\' }}" ng-class="{\'settings-sidebar__link--active\': $ctrl.activeTab === \'security\' }" ng-click="$ctrl.setTab(\'security\'); $event.preventDefault()" ng-keydown="$ctrl.onTabKeydown($event)" >{{ \'Вход и безопасность\' | t }}</a > </nav> </aside> <main class="tw:gutter-half tw:w-full tw:md:w-10/12 settings-content">
1 <div class="settings-verify-banner" ng-if="!$ctrl.emailVerified"> <div class="settings-verify-banner__icon"> <i class="fas fa-envelope"></i> </div> <div class="settings-verify-banner__body"> <div class="settings-verify-banner__text"> {{ \'Подтвердите email, чтобы комментировать и оценивать материалы\' | t }} </div> <div class="settings-verify-banner__actions"> <button type="button" class="settings-verify-banner__btn" ng-click="$ctrl.resendVerificationEmail()" ng-disabled="$ctrl.resendCooldown > 0 || $ctrl.resendLoading" > <i class="fas fa-sync-alt fa-spin" ng-show="$ctrl.resendLoading"></i> <span ng-if="$ctrl.resendCooldown <= 0 && !$ctrl.resendLoading" >{{ \'Отправить письмо подтверждения\' | t }}</span > <span ng-if="$ctrl.resendCooldown > 0" >{{ \'Повторить через\' | t }} {{ $ctrl.resendCooldown }} {{ \'сек\' | t }}</span > </button> <span class="settings-verify-banner__sent" ng-if="$ctrl.resendSuccess"> <i class="fas fa-check"></i> {{ \'Письмо отправлено\' | t }} </span> </div> </div> <button type="button" class="settings-verify-banner__dismiss" ng-click="$ctrl.dismissVerifyBanner()" aria-label="{{ \'Скрыть\' | t }}" > &times; </button> </div> <div ng-if="$ctrl.activeTab === \'about\'" id="panel-about" class="settings-tab-content" role="tabpanel" aria-labelledby="tab-about" > <div class="settings-content__header"> <h1 class="settings-content__title">{{ \'Настройки профиля\' | t }}</h1> <p class="settings-content__subtitle"> {{ \'Здесь можно настроить свой профиль так, как вы хотите\' | t }} </p> </div> <form name="profileForm" ng-submit="$ctrl.saveProfile()"> <div class="settings-section"> <div class="settings-section__title">{{ \'Аватар\' | t }}</div> <div class="settings-avatar"> <div class="settings-avatar__circle" ngf-select="$ctrl.uploadAvatar($file)" ngf-accept="\'image/*\'" ng-disabled="$ctrl.avatarUploading" > <img ng-if="!$ctrl.avatarUploading && $ctrl.author.thumborId" class="settings-avatar__image" ng-src="{{ $ctrl.author.thumborId | imageUrl: 300:300 }}" /> <img ng-if="!$ctrl.avatarUploading && !$ctrl.author.thumborId && $ctrl.author.image" class="settings-avatar__image" ng-src="{{ $ctrl.author.image }}" /> <div ng-if="!$ctrl.avatarUploading && ($ctrl.author.thumborId || $ctrl.author.image)" class="settings-avatar__overlay" > <span class="settings-avatar__overlay-icon"><i class="fas fa-camera"></i></span> <span class="settings-avatar__overlay-text">{{ \'Сменить фото\' | t }}</span> </div> <div ng-if="!$ctrl.avatarUploading && !$ctrl.author.thumborId && !$ctrl.author.image" class="settings-avatar__placeholder" > <span class="settings-avatar__placeholder-icon" ><i class="fas fa-camera"></i ></span> <span class="settings-avatar__placeholder-text" >{{ \'Нажмите, чтобы загрузить свою фотографию\' | t }}</span > </div> <span ng-if="$ctrl.avatarUploading" class="settings-avatar__spinner"> <i class="fas fa-sync-alt fa-spin"></i> </span> </div> </div> </div> <div class="settings-section"> <div class="settings-section__title">{{ \'Имя\' | t }}</div> <div class="settings-field"> <div class="settings-field__input-wrap"> <input class="settings-field__input" type="text" ng-model="$ctrl.displayName" ng-change="$ctrl.onNameChange()" placeholder="{{ \'Как вас зовут?\' | t }}" maxlength="100" required /> <span class="settings-field__counter" ng-class="{\'settings-field__counter--warning\': ($ctrl.displayName || \'\').length > 80, \'settings-field__counter--limit\': ($ctrl.displayName || \'\').length >= 100 }" >{{ 100 - ($ctrl.displayName || \'\').length }}</span > </div> <div class="settings-field__error" ng-if="$ctrl.nameError"> {{ $ctrl.nameError }} </div> <div class="settings-field__hint" ng-if="!$ctrl.nameError"> {{ \'Ваше имя появится на странице профиля и как подпись в публикациях, комментариях и откликах\' | t }} </div> </div> </div> <div class="settings-section"> <div class="settings-section__title">{{ \'О себе\' | t }}</div> <div class="settings-field"> <div class="settings-field__input-wrap settings-field__input-wrap--textarea"> <textarea id="settings-bio" class="settings-field__input settings-field__input--textarea settings-field__input--autogrow" ng-model="$ctrl.author.bio" maxlength="2000" aria-label="{{ \'О себе\' | t }}" placeholder="{{ \'Расскажите немного о себе\' | t }}" ></textarea> <span class="settings-field__counter settings-field__counter--top" ng-if="($ctrl.author.bio || \'\').length > 1800" ng-class="{\'settings-field__counter--warning\': ($ctrl.author.bio || \'\').length > 1800, \'settings-field__counter--limit\': ($ctrl.author.bio || \'\').length >= 2000 }" >{{ 2000 - ($ctrl.author.bio || \'\').length }}</span > </div> </div> </div> <div class="settings-section"> <div class="settings-section__title">{{ \'Адрес на Дискурсе\' | t }}</div> <div class="settings-field"> <div class="settings-address__input-wrap"> <span class="settings-address__prefix">https://discours.io/</span> <input class="settings-address__slug" type="text" ng-model="$ctrl.author.path" ng-change="$ctrl.onPathChange()" ng-blur="$ctrl.onPathBlur()" ng-model-options="{ debounce: { default: 300, blur: 0 } }" required /> </div> <div class="settings-field__error" ng-if="$ctrl.pathError"> {{ $ctrl.pathError }} </div>
1 <div class="settings-field__hint" ng-if="!$ctrl.pathError"> {{ \'Можно писать кириллицей — адрес сам станет латинским (например, «Азат Капенов» → «azat-kapenov»). Строчные латинские буквы, цифры, дефис и подчёркивание, от 3 символов.\' | t }} </div> </div> </div> <div class="settings-section"> <div class="settings-section__title">{{ \'Социальные сети\' | t }}</div> <div class="settings-social"> <div class="settings-social__row"> <div class="settings-social__item"> <span class="settings-social__icon settings-social__icon--facebook">f</span> <input class="settings-social__input" ng-class="{\'settings-social__input--error\': $ctrl.socialErrors.facebook}" type="text" ng-model="$ctrl.author.facebook" ng-change="$ctrl.onSocialChange(\'facebook\')" ng-blur="$ctrl.onSocialBlur(\'facebook\')" aria-label="Facebook" placeholder="https://facebook.com/username" /> </div> <div class="settings-social__error" ng-if="$ctrl.socialErrors.facebook"> {{ $ctrl.socialErrors.facebook }} </div> </div> <div class="settings-social__row"> <div class="settings-social__item"> <span class="settings-social__icon settings-social__icon--telegram"> <i class="fab fa-telegram-plane"></i> </span> <input class="settings-social__input" ng-class="{\'settings-social__input--error\': $ctrl.socialErrors.telegram}" type="text" ng-model="$ctrl.author.telegram" ng-change="$ctrl.onSocialChange(\'telegram\')" ng-blur="$ctrl.onSocialBlur(\'telegram\')" aria-label="Telegram" placeholder="https://t.me/username" /> </div> <div class="settings-social__error" ng-if="$ctrl.socialErrors.telegram"> {{ $ctrl.socialErrors.telegram }} </div> </div> <div class="settings-social__row"> <div class="settings-social__item"> <span class="settings-social__icon settings-social__icon--vk">VK</span> <input class="settings-social__input" ng-class="{\'settings-social__input--error\': $ctrl.socialErrors.vkontakte}" type="text" ng-model="$ctrl.author.vkontakte" ng-change="$ctrl.onSocialChange(\'vkontakte\')" ng-blur="$ctrl.onSocialBlur(\'vkontakte\')" aria-label="VK" placeholder="https://vk.com/username" /> </div> <div class="settings-social__error" ng-if="$ctrl.socialErrors.vkontakte"> {{ $ctrl.socialErrors.vkontakte }} </div> </div> <div class="settings-social__row"> <div class="settings-social__item"> <span class="settings-social__icon settings-social__icon--twitter"> <i class="fab fa-twitter"></i> </span> <input class="settings-social__input" ng-class="{\'settings-social__input--error\': $ctrl.socialErrors.twitter}" type="text" ng-model="$ctrl.author.twitter" ng-change="$ctrl.onSocialChange(\'twitter\')" ng-blur="$ctrl.onSocialBlur(\'twitter\')" aria-label="Twitter" placeholder="https://twitter.com/username" /> </div> <div class="settings-social__error" ng-if="$ctrl.socialErrors.twitter"> {{ $ctrl.socialErrors.twitter }} </div> </div> <div class="settings-social__row"> <div class="settings-social__item"> <span class="settings-social__icon settings-social__icon--instagram"> <i class="fab fa-instagram"></i> </span> <input class="settings-social__input" ng-class="{\'settings-social__input--error\': $ctrl.socialErrors.instagram}" type="text" ng-model="$ctrl.author.instagram" ng-change="$ctrl.onSocialChange(\'instagram\')" ng-blur="$ctrl.onSocialBlur(\'instagram\')" aria-label="Instagram" placeholder="https://instagram.com/username" /> </div> <div class="settings-social__error" ng-if="$ctrl.socialErrors.instagram"> {{ $ctrl.socialErrors.instagram }} </div> </div> <div class="settings-social__row"> <div class="settings-social__item"> <span class="settings-social__icon settings-social__icon--linkedin">in</span> <input class="settings-social__input" ng-class="{\'settings-social__input--error\': $ctrl.socialErrors.linkedin}" type="text" ng-model="$ctrl.author.linkedin" ng-change="$ctrl.onSocialChange(\'linkedin\')" ng-blur="$ctrl.onSocialBlur(\'linkedin\')" aria-label="LinkedIn" placeholder="https://linkedin.com/in/username" /> </div> <div class="settings-social__error" ng-if="$ctrl.socialErrors.linkedin"> {{ $ctrl.socialErrors.linkedin }} </div> </div> <div class="settings-social__row"> <div class="settings-social__item"> <span class="settings-social__icon settings-social__icon--website"> <i class="fas fa-globe"></i> </span> <input class="settings-social__input" ng-class="{\'settings-social__input--error\': $ctrl.socialErrors.website}" type="text" ng-model="$ctrl.author.website" ng-change="$ctrl.onSocialChange(\'website\')" ng-blur="$ctrl.onSocialBlur(\'website\')" aria-label="{{ \'Сайт\' | t }}" placeholder="https://website.com" /> </div> <div class="settings-social__error" ng-if="$ctrl.socialErrors.website"> {{ $ctrl.socialErrors.website }} </div> </div> </div> </div> </form> </div> <div ng-if="$ctrl.activeTab === \'security\'" id="panel-security" class="settings-tab-content" role="tabpanel" aria-labelledby="tab-security" > <div class="settings-content__header"> <h1 class="settings-content__title">{{ \'Настройки входа\' | t }}</h1> <p class="settings-content__subtitle"> {{ \'Управление доступом к вашему аккаунту\' | t }} </p> </div> <form name="securityForm" ng-submit="$ctrl.saveSecurity()"> <div class="settings-section"> <div class="settings-section__title">{{ \'Почта\' | t }}</div> <div class="settings-field"> <div class="settings-field__input-wrap"> <input class="settings-field__input" type="email" inputmode="email" autocomplete="email" ng-model="$ctrl.author.email" aria-label="{{ \'Электронная почта\' | t }}" /> </div> </div> </div> <div class="settings-section"> <div class="settings-section__title">{{ \'Пароль\' | t }}</div> <div class="settings-field settings-field--spaced"> <div class="settings-section__label">{{ \'Новый пароль\' | t }}</div> <div class="settings-field__input-wrap"> <input class="settings-field__input" type="password" ng-model="$ctrl.newPassword" minlength="8" autocomplete="new-password" placeholder="{{ \'Минимум 8 символов\' | t }}" aria-label="{{ \'Новый пароль\' | t }}" /> </div> </div> <div class="settings-field settings-field--confirm-password" ng-if="$ctrl.newPassword" > <div class="settings-section__label">{{ \'Подтвердите новый пароль\' | t }}</div> <div class="settings-field__input-wrap"> <input class="settings-field__input" type="password" ng-model="$ctrl.confirmPassword" autocomplete="new-password" aria-label="{{ \'Подтвердите новый пароль\' | t }}" /> </div> <div class="settings-field__error" ng-if="$ctrl.passwordError"> {{ $ctrl.passwordError }} </div> </div> </div> <div class="settings-section"> <div class="settings-section__title">{{ \'Вход через соцсети\' | t }}</div> <div class="settings-oauth"> <div class="settings-oauth__item"> <span class="settings-oauth__label">Google</span> <span class="settings-oauth__connected" ng-if="$ctrl.oauthConnected.google"> <span class="settings-oauth__provider-icon settings-oauth__provider-icon--google" >G</span > <span>{{ \'Подключён\' | t }}</span> <button type="button" class="settings-oauth__disconnect" ng-click="$ctrl.disconnectOAuth(\'google\')" ng-disabled="$ctrl.oauthLoading.google" > &times; </button>
1 </span> <button type="button" class="settings-oauth__connect" ng-if="!$ctrl.oauthConnected.google" ng-click="$ctrl.connectOAuth(\'google\')" ng-disabled="$ctrl.oauthLoading.google" ng-class="{\'settings-oauth__connect--loading\': $ctrl.oauthLoading.google }" > <span class="settings-oauth__provider-icon" style="color: #fff">G</span> <span ng-if="!$ctrl.oauthLoading.google">{{ \'Привязать\' | t }}</span> <span ng-if="$ctrl.oauthLoading.google">{{ \'Подключение…\' | t }}</span> </button> </div> <div class="settings-oauth__item"> <span class="settings-oauth__label">GitHub</span> <span class="settings-oauth__connected" ng-if="$ctrl.oauthConnected.github"> <span class="settings-oauth__provider-icon settings-oauth__provider-icon--github" > <i class="fab fa-github"></i> </span> <span>{{ \'Подключён\' | t }}</span> <button type="button" class="settings-oauth__disconnect" ng-click="$ctrl.disconnectOAuth(\'github\')" ng-disabled="$ctrl.oauthLoading.github" > &times; </button> </span> <button type="button" class="settings-oauth__connect" ng-if="!$ctrl.oauthConnected.github" ng-click="$ctrl.connectOAuth(\'github\')" ng-disabled="$ctrl.oauthLoading.github" ng-class="{\'settings-oauth__connect--loading\': $ctrl.oauthLoading.github }" > <span class="settings-oauth__provider-icon" style="color: #fff"> <i class="fab fa-github"></i> </span> <span ng-if="!$ctrl.oauthLoading.github">{{ \'Привязать\' | t }}</span> <span ng-if="$ctrl.oauthLoading.github">{{ \'Подключение…\' | t }}</span> </button> </div> <div class="settings-oauth__item"> <span class="settings-oauth__label">Facebook</span> <span class="settings-oauth__connected" ng-if="$ctrl.oauthConnected.facebook"> <span class="settings-oauth__provider-icon settings-oauth__provider-icon--facebook" > <i class="fab fa-facebook-f"></i> </span> <span>{{ \'Подключён\' | t }}</span> <button type="button" class="settings-oauth__disconnect" ng-click="$ctrl.disconnectOAuth(\'facebook\')" ng-disabled="$ctrl.oauthLoading.facebook" > &times; </button> </span> <button type="button" class="settings-oauth__connect" ng-if="!$ctrl.oauthConnected.facebook" ng-click="$ctrl.connectOAuth(\'facebook\')" ng-disabled="$ctrl.oauthLoading.facebook" ng-class="{\'settings-oauth__connect--loading\': $ctrl.oauthLoading.facebook }" > <span class="settings-oauth__provider-icon" style="color: #fff"> <i class="fab fa-facebook-f"></i> </span> <span ng-if="!$ctrl.oauthLoading.facebook">{{ \'Привязать\' | t }}</span> <span ng-if="$ctrl.oauthLoading.facebook">{{ \'Подключение…\' | t }}</span> </button> </div> <div class="settings-oauth__item"> <span class="settings-oauth__label">VK</span> <span class="settings-oauth__connected" ng-if="$ctrl.oauthConnected.vk"> <span class="settings-oauth__provider-icon settings-oauth__provider-icon--vk"> <i class="fab fa-vk"></i> </span> <span>{{ \'Подключён\' | t }}</span> <button type="button" class="settings-oauth__disconnect" ng-click="$ctrl.disconnectOAuth(\'vk\')" ng-disabled="$ctrl.oauthLoading.vk" > &times; </button> </span> <button type="button" class="settings-oauth__connect" ng-if="!$ctrl.oauthConnected.vk" ng-click="$ctrl.connectOAuth(\'vk\')" ng-disabled="$ctrl.oauthLoading.vk" ng-class="{\'settings-oauth__connect--loading\': $ctrl.oauthLoading.vk }" > <span class="settings-oauth__provider-icon" style="color: #fff"> <i class="fab fa-vk"></i> </span> <span ng-if="!$ctrl.oauthLoading.vk">{{ \'Привязать\' | t }}</span> <span ng-if="$ctrl.oauthLoading.vk">{{ \'Подключение…\' | t }}</span> </button> </div> </div> </div> <div class="settings-section"> <div class="settings-section__title">{{ \'Telegram-бот\' | t }}</div> <div class="settings-telegram" ng-if="$ctrl.telegramLinked"> <div class="settings-telegram__status"> <span class="settings-telegram__icon" aria-hidden="true">✓</span> <div class="settings-telegram__status-text"> <strong>{{ \'Telegram привязан\' | t }}</strong> <span class="settings-telegram__username" ng-if="$ctrl.telegramUsername" >@{{ $ctrl.telegramUsername }}</span > </div> </div> <p class="settings-telegram__hint"> {{ \'В боте доступны\' | t }} <a ng-href="{{ $ctrl.telegramBotLink }}" target="_blank" rel="noopener" >@DiscoursPostBot</a >: <code>/myqueue</code> — {{ \'статусы ваших материалов\' | t }}, <code>/queue</code> — {{ \'очередь редакции (если есть роль)\' | t }}. {{ \'Чтобы отвязать — отправьте\' | t }} <code>/unlink</code> {{ \'в бота\' | t }}. </p> </div>
1 <div class="settings-telegram" ng-if="!$ctrl.telegramLinked && $ctrl.telegramCode"> <p class="settings-telegram__hint"> {{ \'Откройте\' | t }} <a ng-href="{{ $ctrl.telegramBotLink }}" target="_blank" rel="noopener" >@DiscoursPostBot</a > {{ \'и отправьте команду с кодом\' | t }}: </p> <div class="settings-telegram__code-wrap"> <div class="settings-telegram__code" aria-label="{{ \'Одноразовый код привязки\' | t }}" > <code>/link {{ $ctrl.telegramCode }}</code> </div> <button type="button" class="settings-telegram__copy" ng-click="$ctrl.copyTelegramCode()" ng-disabled="$ctrl.telegramCodeCopied" aria-label="{{ \'Скопировать код\' | t }}" > <span ng-if="!$ctrl.telegramCodeCopied">{{ \'Копировать\' | t }}</span> <span ng-if="$ctrl.telegramCodeCopied">✓ {{ \'Скопировано\' | t }}</span> </button> </div> <div class="settings-telegram__meta"> <span class="settings-telegram__countdown" aria-live="polite" >⏱ {{ \'Код действует\' | t }}: {{ $ctrl.telegramCountdownText() }}</span > <button type="button" class="settings-telegram__check" ng-click="$ctrl.checkTelegramLinkStatus()" > {{ \'Я ввёл код\' | t }} </button> <button type="button" class="settings-telegram__cancel" ng-click="$ctrl.cancelTelegramLink()" > {{ \'Отмена\' | t }} </button> </div> <p class="settings-telegram__hint settings-telegram__hint--small"> {{ \'Как только введёте код в боте, страница сама обновится\' | t }}. </p> </div> <div class="settings-telegram" ng-if="!$ctrl.telegramLinked && !$ctrl.telegramCode"> <p class="settings-telegram__hint"> {{ \'Привяжите Telegram-аккаунт, чтобы получать в @DiscoursPostBot статусы своих материалов и редакционные уведомления\' | t }}. </p> <button type="button" class="settings-oauth__connect" ng-click="$ctrl.generateTelegramCode()" ng-disabled="$ctrl.telegramGenerating" ng-class="{\'settings-oauth__connect--loading\': $ctrl.telegramGenerating }" > <span class="settings-oauth__provider-icon" style="color: #fff" aria-hidden="true" >✈</span > <span ng-if="!$ctrl.telegramGenerating">{{ \'Привязать Telegram\' | t }}</span> <span ng-if="$ctrl.telegramGenerating">{{ \'Генерируем код…\' | t }}</span> </button> </div> </div> </form> </div> <div ng-if="$ctrl.activeTab === \'notifications\'" id="panel-notifications" class="settings-tab-content" role="tabpanel" aria-labelledby="tab-notifications" > <div class="settings-content__header"> <h1 class="settings-content__title">{{ \'Уведомления\' | t }}</h1> <p class="settings-content__subtitle"> {{ \'Выберите, какие уведомления вы хотите получать\' | t }} </p> </div> <form class="settings-notif-form" ng-submit="$ctrl.saveNotifications()"> <table class="settings-notif-table"> <thead> <tr> <th>{{ \'Тип\' | t }}</th> <th>{{ \'На сайте\' | t }}</th> <th>{{ \'Email\' | t }}</th> </tr> </thead> <tbody> <tr> <td>{{ \'Комментарии\' | t }}</td> <td> <input type="checkbox" ng-model="$ctrl.notifPrefs.comments_site" aria-label="{{ \'Комментарии на сайте\' | t }}" /> </td> <td> <input type="checkbox" ng-model="$ctrl.notifPrefs.comments_email" aria-label="{{ \'Комментарии по email\' | t }}" /> </td> </tr> <tr> <td>{{ \'Ремарки\' | t }}</td> <td> <input type="checkbox" ng-model="$ctrl.notifPrefs.remarks_site" aria-label="{{ \'Ремарки на сайте\' | t }}" /> </td> <td> <input type="checkbox" ng-model="$ctrl.notifPrefs.remarks_email" aria-label="{{ \'Ремарки по email\' | t }}" /> </td> </tr> <tr> <td>{{ \'Упоминания\' | t }}</td> <td> <input type="checkbox" ng-model="$ctrl.notifPrefs.mentions_site" aria-label="{{ \'Упоминания на сайте\' | t }}" /> </td> <td> <input type="checkbox" ng-model="$ctrl.notifPrefs.mentions_email" aria-label="{{ \'Упоминания по email\' | t }}" /> </td> </tr> <tr> <td>{{ \'Приглашения\' | t }}</td> <td> <input type="checkbox" ng-model="$ctrl.notifPrefs.invites_site" aria-label="{{ \'Приглашения на сайте\' | t }}" /> </td> <td> <input type="checkbox" ng-model="$ctrl.notifPrefs.invites_email" aria-label="{{ \'Приглашения по email\' | t }}" /> </td> </tr> <tr> <td>{{ \'Публикации\' | t }}</td> <td> <input type="checkbox" ng-model="$ctrl.notifPrefs.published_site" aria-label="{{ \'Публикации на сайте\' | t }}" /> </td> <td> <input type="checkbox" ng-model="$ctrl.notifPrefs.published_email" aria-label="{{ \'Публикации по email\' | t }}" /> </td> </tr> <tr> <td>{{ \'Правки (предложения)\' | t }}</td> <td> <input type="checkbox" ng-model="$ctrl.notifPrefs.suggestions_site" aria-label="{{ \'Правки на сайте\' | t }}" /> </td> <td> <input type="checkbox" ng-model="$ctrl.notifPrefs.suggestions_email" aria-label="{{ \'Правки по email\' | t }}" /> </td> </tr> <tr> <td>{{ \'Сообщения\' | t }}</td> <td> <input type="checkbox" ng-model="$ctrl.notifPrefs.messages_site" aria-label="{{ \'Сообщения на сайте\' | t }}" /> </td> <td> <input type="checkbox" ng-model="$ctrl.notifPrefs.messages_email" aria-label="{{ \'Сообщения по email\' | t }}" /> </td> </tr> </tbody> </table> <div class="settings-notif-actions"> <button type="submit" class="ds-btn ds-btn-primary" ng-disabled="$ctrl.notifSaving || !$ctrl.hasNotifChanges()" > {{ $ctrl.notifSaving ? (\'Сохранение...\' | t) : (\'Сохранить\' | t) }} </button> </div> </form> </div> <div ng-if="$ctrl.activeTab === \'subscriptions\'" id="panel-subscriptions" class="settings-tab-content" role="tabpanel" aria-labelledby="tab-subscriptions" > <div class="settings-content__header"> <h1 class="settings-content__title">{{ \'Подписки\' | t }}</h1> <p class="settings-content__subtitle"> {{ \'Здесь можно управлять всеми своими подписками Ð
1½Ð° сайте\' | t }} </p> </div> <div class="settings-sub-tabs"> <button type="button" class="settings-sub-tab" ng-class="{\'settings-sub-tab--active\': $ctrl.subTab === \'all\' }" ng-click="$ctrl.setSubTab(\'all\')" > {{ \'Все\' | t }} <span class="settings-sub-tab__count">{{ $ctrl.subscriptionCounts.all }}</span> </button> <button type="button" class="settings-sub-tab" ng-class="{\'settings-sub-tab--active\': $ctrl.subTab === \'users\' }" ng-click="$ctrl.setSubTab(\'users\')" > {{ \'Пользователи\' | t }} <span class="settings-sub-tab__count">{{ $ctrl.subscriptionCounts.users }}</span> </button> <button type="button" class="settings-sub-tab" ng-class="{\'settings-sub-tab--active\': $ctrl.subTab === \'tags\' }" ng-click="$ctrl.setSubTab(\'tags\')" > {{ \'Темы\' | t }} <span class="settings-sub-tab__count">{{ $ctrl.subscriptionCounts.tags }}</span> </button> </div> <div class="settings-search"> <div class="settings-search__wrap"> <input class="settings-search__input" type="text" ng-model="$ctrl.subscriptionSearch" ng-change="$ctrl.filterSubscriptions()" ng-model-options="{ debounce: 300 }" placeholder="{{ \'Поиск\' | t }}" /> <i class="fas fa-search settings-search__icon"></i> </div> </div> <div class="settings-subscription-list" ng-if="$ctrl.subscriptionsLoading"> <div class="settings-skeleton-item" ng-repeat="i in [1,2,3,4,5] track by $index"> <div class="settings-skeleton-item__avatar settings-skeleton-pulse"></div> <div class="settings-skeleton-item__info"> <div class="settings-skeleton-item__name settings-skeleton-pulse"></div> <div class="settings-skeleton-item__desc settings-skeleton-pulse"></div> </div> <div class="settings-skeleton-item__btn settings-skeleton-pulse"></div> </div> </div> <div class="settings-subscription-list" ng-if="!$ctrl.subscriptionsLoading"> <a class="settings-subscription-item" ng-repeat="item in $ctrl.filteredSubscriptions track by (item.type + \':\' + item.id)" ng-href="{{ $ctrl.getSubscriptionLink(item) }}" > <img ng-if="item.type === \'user\' && item.thumborId" class="settings-subscription-item__avatar" ng-src="{{ item.thumborId | imageUrl: 96:96 }}" /> <div ng-if="item.type === \'user\' && !item.thumborId" class="settings-subscription-item__avatar settings-subscription-item__avatar--placeholder" > <i class="fas fa-user"></i> </div> <div ng-if="item.type === \'tag\'" class="settings-subscription-item__topic-icon"> <span ng-include="\'/partials/icons/hashtag.html\'"></span> </div> <div class="settings-subscription-item__info"> <div class="settings-subscription-item__name" ng-class="{\'settings-subscription-item__name--topic\': item.type === \'tag\' }" > {{ item.name }} </div> <div class="settings-subscription-item__description" ng-if="item.description || item.itemsCount" > {{ item.description || (item.itemsCount | pluralize:\'публикация\':\'публикации\':\'публикаций\') }} </div> </div> <div class="settings-subscription-item__action" ng-if="!$ctrl.isCurrentUser(item)"> <button type="button" class="settings-subscription-item__btn" ng-class="{\'settings-subscription-item__btn--subscribed\': item.isSubscribed, \'settings-subscription-item__btn--subscribe\': !item.isSubscribed }" ng-disabled="item.loading" ng-click="$ctrl.toggleFollow(item, $event)" > <span ng-if="!item.loading" >{{ item.isSubscribed ? (\'Вы подписаны\' | t) : (\'Подписаться\' | t) }}</span > <i ng-if="item.loading" class="fas fa-sync-alt fa-spin"></i> </button> </div> </a> <div class="settings-field__hint" style="text-align: center; padding: 12px 0" ng-if="$ctrl.hasMoreSubscriptions && $ctrl.filteredSubscriptions.length > 0" > {{ \'Показаны первые\' | t }} {{ $ctrl.subscriptionCounts.all }} {{ \'подписок. Полный список доступен на вашей странице профиля.\' | t }} </div> <div class="settings-placeholder" ng-if="$ctrl.filteredSubscriptions.length === 0"> <div class="settings-placeholder__icon"> <i class="fas fa-users"></i> </div> <div class="settings-placeholder__title" ng-if="!$ctrl.subscriptionSearch"> {{ \'У вас пока нет подписок\' | t }} </div>
1 <div class="settings-placeholder__text" ng-if="!$ctrl.subscriptionSearch"> <a href="/authors" class="linklike">{{ \'Найти авторов\' | t }}</a> </div> <div class="settings-placeholder__title" ng-if="$ctrl.subscriptionSearch"> {{ \'Ничего не найдено\' | t }} </div> <div class="settings-placeholder__text" ng-if="$ctrl.subscriptionSearch"> {{ \'Попробуйте изменить запрос\' | t }} </div> </div> </div> </div> </main> </div> </div> </div> <div class="settings-actions settings-actions--sticky" ng-if="$ctrl.authorized && $ctrl.hasUnsavedChanges" > <div class="ds-container"> <div class="tw:row-flex"> <div class="tw:gutter-half tw:w-full tw:md:w-10/12 tw:md:ml-offset-2"> <div class="settings-actions__inner"> <button type="button" class="settings-actions__cancel" ng-click="$ctrl.cancelChanges()"> {{ \'Отменить изменения\' | t }} </button> <button type="button" class="settings-actions__save" ng-disabled="$ctrl.savingProfile || $ctrl.savingSecurity" ng-click="$ctrl.activeTab === \'security\' ? $ctrl.saveSecurity() : $ctrl.saveProfile()" > <span ng-hide="$ctrl.savingProfile || $ctrl.savingSecurity" >{{ \'Сохранить настройки\' | t }}</span > <i ng-show="$ctrl.savingProfile || $ctrl.savingSecurity" class="fas fa-sync-alt fa-spin" ></i> </button> </div> </div> </div> </div> </div>'),s.put("/pages/edit/editContentItem/customTypeahead.html",'<a> <span ng-bind-html="match.label | typeaheadHighlight:query"></span> <span class="tag-counter" ng-bind="match.model.itemsCount"></span> </a>'),s.put("/pages/edit/editContentItem/editContentItem.html",'<link rel="stylesheet" href="/styles/editor-e839e09a.css" /> <auth-required-screen ng-if="$ctrl.requiresAuth" title="{{ \'Войдите для доступа к&amp;nbsp;материалу\' | t }}" description="{{ \'Для редактирования материала<br>необходимо войти в&amp;nbsp;свой аккаунт\' | t }}" icon="fas fa-edit" button-text="{{ \'Войти\' | t }}" on-login-click="$ctrl.openLoginModal()" ></auth-required-screen> <div class="auth-required-screen" ng-cloak ng-if="!$ctrl.requiresAuth && $ctrl.accessDenied"> <update-meta title="{{ \'Не удается открыть страницу\' | t }}"></update-meta> <update-meta name="robots" content="noindex, nofollow"></update-meta> <div class="ds-container"> <div class="tw:row-flex"> <div class="tw:gutter-half tw:w-full tw:md:w-6/12 tw:md:ml-offset-3 tw:text-center"> <div class="auth-required-content"> <i class="{{ $ctrl.accessDeniedIcon ||\'fas fa-lock\' }} auth-required-icon"></i> <h2>{{ ($ctrl.accessDeniedTitle || \'Не удается открыть страницу\') | t }}</h2> <p class="auth-required-description" ng-if="$ctrl.accessDeniedMessage"> {{ $ctrl.accessDeniedMessage | t }} </p> <p class="auth-required-description" ng-if="!$ctrl.accessDeniedMessage"> {{ \'Возможно, страницы не существует\' | t }}<br />{{ \'или к ней нет доступа\' | t }} </p> <p class="auth-required-description" ng-if="$ctrl.accessDeniedEmailUnverified"> {{ \'Если приглашение прислали на почту, подтвердите адрес — доступ откроется сразу после этого.\' | t }} </p> <div class="auth-required-actions"> <button type="button" ng-if="$ctrl.canRetryEditorLoad" ng-click="$ctrl.retryEditorLoad()" class="ds-btn ds-btn-primary" > {{ \'Повторить\' | t }} </button> <a href="/" ng-class="$ctrl.canRetryEditorLoad ? \'auth-required-btn-secondary\' : \'ds-btn ds-btn-primary\'" >{{ \'На главную\' | t }}</a > <a ng-if="$ctrl.accessDeniedLink" ng-href="{{ $ctrl.accessDeniedLink }}" class="auth-required-btn-secondary" >{{ \'Открыть материал\' | t }}</a > <button ng-if="$ctrl.canRequestAccess" ng-click="$ctrl.showAccessRequestModal = true" class="auth-required-btn-secondary" > {{ \'Запросить доступ\' | t }} </button> <button type="button" ng-if="$ctrl.accessDeniedEmailUnverified" ng-click="$ctrl.openEmailVerification()" class="auth-required-btn-secondary" > {{ \'Подтвердить почту\' | t }} </button> </div> <p ng-if="$ctrl.accessRequestSent" class="tw:mt-4 tw:text-success"> {{ \'Запрос на доступ отправлен\' | t }} </p> </div> </div> </div> </div> <request-contribution ng-if="$ctrl.showAccessRequestModal" document-name="$ctrl.accessRequestDocName" opened="$ctrl.showAccessRequestModal" on-close
1="$ctrl.showAccessRequestModal = false" on-sent-change="$ctrl.onAccessRequestSent(sent)" ></request-contribution> </div> <div class="ds-container" ng-if="!$ctrl.requiresAuth && !$ctrl.accessDenied"> <ng-include src="\'/pages/other/error.html\'" ng-if="contentItem.error" ng-init="$root.statusCode = contentItem.error.value" ></ng-include> </div> <div class="ds-container post-form has-tiptap-editor" ng-class="{\'has-admin-tools\': $ctrl.isAdmin}" ng-if="!$ctrl.requiresAuth && !$ctrl.accessDenied && !contentItem.error && ($ctrl.types[contentItem.type] || $ctrl.isGalleryLoading)" ng-cloak > <update-meta title="{{ title }}" ng-if="title"></update-meta> <update-meta name="robots" content="noindex, nofollow"></update-meta> <update-meta ng-if="$ctrl.types[contentItem.type]" name="description" content="{{ $ctrl.types[contentItem.type].description }}" ></update-meta> <update-meta name="keywords" content="{{ \'Discours.io, стать автором, песочница\' | t }}" ></update-meta> <update-meta ng-if="$ctrl.types[contentItem.type]" property="og:title" content="{{ $ctrl.types[contentItem.type].og_title }}" ></update-meta> <update-meta ng-if="$ctrl.types[contentItem.type]" property="og:description" content="{{ $ctrl.types[contentItem.type].og_description }}" ></update-meta> <update-meta property="og:image" content="/images/joinfull.webp"></update-meta> <update-meta property="og:image:width" content="1800"></update-meta> <update-meta property="og:image:height" content="1083"></update-meta> <form class="discours-form" name="articleForm" novalidate ng-class="{\'editor-form-ready\': $ctrl.tiptapReady || $ctrl.tiptapExpoEditor || $ctrl.album || (!$ctrl.useTiptapEditor && !$ctrl.useTiptapExpoEditor) }" > <div class="editor-content-row tw:flex tw:flex-wrap tw:mt-20"> <div class="editor-main-col tw:sm:w-8/12 tw:md:ml-offset-2"> <div ng-if="$ctrl.isGalleryLoading" class="editor-skeleton"> <div class="skeleton-title"></div> <div class="skeleton-gallery-row"> <div class="skeleton-image"></div> <div class="skeleton-content"> <div class="skeleton-subtitle"></div> <div class="skeleton-text"></div> <div class="skeleton-text skeleton-text--85"></div> <div class="skeleton-text skeleton-text--70"></div> </div> </div> <div class="skeleton-gallery-row"> <div class="skeleton-image"></div> <div class="skeleton-content"> <div class="skeleton-subtitle"></div> <div class="skeleton-text"></div> <div class="skeleton-text skeleton-text--90"></div> </div> </div> </div> <ng-include ng-if="contentItem.type && !$ctrl.album" src="\'/pages/edit/editContentItem/types/\' + contentItem.type + \'.html\'" ></ng-include> <ng-include ng-if="contentItem.type && $ctrl.album && !$ctrl.isGalleryLoading" src="\'/pages/edit/editContentItem/types/\' + contentItem.type + \'Album.html\'" ></ng-include> </div> </div> <div class="category-selector-button-wrapper tw:flex tw:flex-wrap" ng-class="{ active: $ctrl.categorySelectorOpen }" ng-hide="$ctrl.isPreviewMode" > <div class="category-selector-col tw:sm:w-8/12 tw:md:ml-offset-2 tw:-mt-2.5"> <div class="form-flex-layout tw:flex tw:flex-wrap"> <div class="category-label-col tw:w-3/12 form-label-title"> <div class="tags-title" ng-if=" contentItem.type != \'Literature\' && contentItem.type != \'Music\' && contentItem.type != \'Video\' && contentItem.type != \'Image\' " > {{ \'рубрика:\' | t }} </div> <div class="tags-title" ng-if=" contentItem.type == \'Literature\' || contentItem.type == \'Music\' || contentItem.type == \'Video\' || contentItem.type == \'Image\' " > {{ \'жанр:\' | t }} </div> </div> <div class="category-input-col tw:sm:w-8/12"> <button type="button" class="category-selector-button" ng-class="{\'category-selector-button--error\': formErrors.category}" ng-click="$ctrl.categorySelectorClick($event)" id="category-selector-button" aria-invalid="{{ !!formErrors.category }}" aria-describedby="form-error-category" > <span id="category-selector-text" class="placeholder" ng-if="!contentItem.selectedCategory" >{{ \'выбрать\' | t }} </span> <span ng-if="contentItem.selectedCategory" id="category-selector-text"> {{ contentItem.selectedCategory.title | t }} </span> </button> <ul class="sort-menu post-menu category-selector" ng-class="{ active: $ctrl.categorySelectorOpen }" > <li ng-repeat="category in $ctrl.categories track by category._id" ng-class="{ active: contentItem.selectedCategory == category }" > <a ng-click="contentItem.selectedCategory = category; formErrors.category = false; $ctrl.categorySelectorOpen = false;" >{{ category.title | t }}</a > </li> </ul> <span id="form-error-category" class="help-block" ng-show="formErrors.category" role="alert" >{{ \'Выберите рубрику\' | t }}</span > </div> </div> </div> </div> <div class="tags-row-wrapper tw:flex tw:flex-wrap" ng-hide="$ctrl.isPreviewMode"> <div class="tags-row-col tw:sm:w-8/12 tw:md:ml-offset-2 tw:-mt-[5px]"> <div class="form-flex-layout tw:flex tw:flex-wrap"> <div class="tags-label-col tw:w-3/12 form-label-title"> <div class="tags-title">{{ \'темы:\' | t }}</div> </div> <div class="tags-input-col tw:sm:w-8/12 tw:lg:w-10/12"> <ul class="tags edit"> <li ng-repeat="tag in contentItem.tags track by (tag._id || tag.title || $index)"> <a ng-click="$ctrl.removeTag(tag)">{{ tag.title }}<span class="times">×</span></a> </li> <li> <input type="text" placeholder="{{ \'добавить тему\' | t }}" class="tags-input tags-auto-width" ng-model="$ctrl.newTag" simple-typeahead="tag as tag.title for tag in $ctrl.tagsTypeahead($viewValue)" typeahead-loading="$ctrl.tagLoading" typeahead-on-select="$ctrl.inputSelect($item, $model, $label)" typeahead-template-url="/pages/edit/editContentItem/customTypeahead.html" aria-label="{{ \'Добавить тему\' | t }}" /><span class="tags-spinner" style=" display: inline-block; width: 1.25em; margin-left: 6px; vertical-align: middle; " ><i class="fas fa-sync-alt fa-spin" ng-show="$ctrl.tagLoading"></i ></span> </li> </ul> </div> </div> </div> </div> <div class="editor-content-row tw:flex tw:flex-wrap tw:mt-20 tw:mb-2.5" ng-if="!$
1ctrl.isLoggedIn && !$ctrl.isPreviewMode && !$ctrl.contentItemCreatorId" > <div class="editor-main-col tw:sm:w-8/12 tw:md:ml-offset-2"> <h1 class="category-title big" ng-if="!$ctrl.isLoggedIn">{{ \'РАССКАЖИТЕ О СЕБЕ\' | t }}</h1> <h1 class="category-title big" ng-if="$ctrl.isLoggedIn"> <u>{{ $ctrl.types[contentItem.type].title }}</u> </h1> </div> </div> <div class="tw:flex tw:flex-wrap tw:mt-section" ng-if="!$ctrl.isLoggedIn && !$ctrl.isPreviewMode && !$ctrl.contentItemCreatorId" > <div class="editor-main-col tw:sm:w-8/12 tw:md:ml-offset-2"> <div class="app-about"> <div class="app-about__row" ng-class="{\'has-error\': formErrors.applicationName}"> <input class="app-about__input" placeholder="{{ \'Как вас зовут?\' | t }}" aria-label="{{ \'Ваше имя и фамилия\' | t }}" ng-model="contentItem.application.name" ng-model-options="{ updateOn: \'blur\', debounce: 1000 }" ng-keydown="formErrors.applicationName = false" ng-change="$ctrl.sendNotSentApplication()" clear-blank-on-blur aria-invalid="{{ !!formErrors.applicationName }}" aria-describedby="form-error-app-name" /> <div class="app-about__note"> <span id="form-error-app-name" class="app-about__error" ng-show="formErrors.applicationName" role="alert" >{{ \'Укажите имя\' | t }}</span > </div> </div> <div class="app-about__row" ng-class="{\'has-error\': formErrors.applicationEmail}"> <input class="app-about__input" placeholder="{{ \'Ваша почта\' | t }}" aria-label="{{ \'Электронная почта\' | t }}" ng-model="contentItem.application.email" type="email" inputmode="email" ng-model-options="{ debounce: 1000 }" ng-keydown="formErrors.applicationEmail = false" ng-change="$ctrl.sendNotSentApplication()" clear-blank-on-blur aria-invalid="{{ !!formErrors.applicationEmail }}" aria-describedby="app-hint-email form-error-app-email" /> <div class="app-about__note"> <span class="app-about__hint" id="app-hint-email" >{{ \'Для ответа редакции\' | t }}</span > <span id="form-error-app-email" class="app-about__error" ng-show="formErrors.applicationEmail" role="alert" >{{ \'Укажите корректный email\' | t }}</span > </div> </div> <div class="app-about__row app-about__row--bio"> <textarea class="app-about__input app-about__input--bio" rows="1" ng-model="contentItem.application.bio" placeholder="{{ \'Чем вы занимаетесь?\' | t }}" aria-label="{{ \'Биография автора\' | t }}" msd-elastic="msd-elastic" ng-model-options="{ debounce: 1000 }" ng-change="$ctrl.sendNotSentApplication()" clear-blank-on-blur aria-describedby="app-hint-bio" ></textarea> <div class="app-about__note"> <span class="app-about__hint" id="app-hint-bio" >{{ \'Расскажите немного о себе, этот текст появится в вашем профиле\' | t }}</span > </div> </div> </div> </div> </div> <div class="edit-actions tw:sm:w-8/12 tw:md:ml-offset-2" ng-hide="$ctrl.isPreviewMode"> <div class="edit-actions__row"> <div class="edit-actions__main"> <button ng-if="$ctrl.canEdit === true || !$ctrl.isLoggedIn" class="edit-action-btn edit-action-btn--primary ng-animate-disabled" type="button" ng-disabled="contentItemSending || $ctrl.disableSendButton" ng-click="$ctrl.sendForm($event)" > <span ng-class="{\'edit-button-text--hidden\': sendingAction === \'submit\'}" >{{ \'Опубликовать\' | t }}</span ><i class="fas fa-sync-alt fa-spin edit-button-spinner ng-animate-disabled" ng-if="sendingAction === \'submit\'" ></i> </button> <button ng-if="$ctrl.canEdit === false" class="edit-action-btn edit-action-btn--primary ng-animate-disabled" type="button" ng-click="$ctrl.finishAndNavigateToArticle()" > {{ \'Готово\' | t }} </button> <button ng-if="$ctrl.canEdit === true && $ctrl.isLoggedIn && (!contentItem.slug || $ctrl.saveAndCreateVersion)" class="edit-action-btn edit-action-btn--secondary ng-animate-disabled" type="button" ng-disabled="contentItemSending || $ctrl.disableSendButton" ng-click="$ctrl.saveDraftBtnClick($event)" title="{{ \'Сохранить черновик\' | t }}" > <span ng-class="{\'edit-button-text--hidden\': sendingAction === \'draft\'}" >{{ \'Сохранить черновик\' | t }}</span ><i class="fas fa-sync-alt fa-spin edit-button-spinner ng-animate-disabled" ng-if="sendingAction === \'draft\'" ></i> </button>
1 <button class="edit-action-btn edit-action-btn--ghost" type="button" ng-click="$ctrl.openPreviewModeAndScrollTop()" > {{ \'Предпросмотр\' | t }} </button> </div> <button class="edit-action-btn edit-action-btn--danger" ng-click="$ctrl.deleteContentItem(contentItem.slug)" type="button" ng-if="contentItem._id && ($ctrl.isAdmin || $ctrl.canDeleteOwn())" > {{ \'Удалить\' | t }} </button> </div> </div> <div class="version-history-panel" ng-if="$ctrl.versionsVisible" ng-class="{\'with-preview\': $ctrl.selectedVersion }" > <div class="version-history-header"> <h3>{{ \'История версий\' | t }}</h3> <div class="version-header-actions" ng-if="!$ctrl.selectedVersion"> <button class="version-header-save-btn ng-animate-disabled" type="button" ng-click="$ctrl.openVersionNameDialog()" ng-disabled="$ctrl.savingVersion" title="{{ \'Сохранить текущее состояние как версию\' | t }}" > <span ng-if="$ctrl.savingVersion"><i class="fa fa-spinner fa-spin"></i></span> <span ng-if="!$ctrl.savingVersion">{{ \'Сохранить версию\' | t }}</span> </button> <button type="button" class="close-btn" ng-click="$ctrl.toggleVersions()" title="{{ \'Закрыть\' | t }}" > <i class="fa fa-times"></i> </button> </div> </div> <div class="version-stats" ng-if="$ctrl.versionStats && $ctrl.versionStats.total > 0"> <span title="{{ \'Автосохранения\' | t }}" ng-if="$ctrl.versionStats.counts.auto > 0"> <i class="fa fa-clock-o"></i> {{ $ctrl.versionStats.counts.auto }} </span> <span title="{{ \'Крупные изменения\' | t }}" ng-if="$ctrl.versionStats.counts.milestone > 0"> <i class="fa fa-flag"></i> {{ $ctrl.versionStats.counts.milestone }} </span> <span title="{{ \'Сохранено вручную\' | t }}" ng-if="$ctrl.versionStats.counts.manual > 0"> <i class="fa fa-save"></i> {{ $ctrl.versionStats.counts.manual }} </span> <span title="{{ \'При публикации\' | t }}" ng-if="$ctrl.versionStats.counts.publish > 0"> <i class="fa fa-check-circle"></i> {{ $ctrl.versionStats.counts.publish }} </span> </div> <div class="version-loading" ng-if="$ctrl.versionsLoading"> <i class="fa fa-spinner fa-spin"></i> {{ \'Загрузка...\' | t }} </div> <div class="version-list" ng-if="!$ctrl.versionsLoading"> <div class="version-empty" ng-if="$ctrl.versions.length === 0"> <p>{{ \'Версии ещё не созданы\' | t }}</p> <p class="hint"> {{ \'Версии создаются автоматически каждые 20 минут, при публикации и при значительных изменениях.\' | t }} </p> </div> <div class="version-item" ng-repeat="version in $ctrl.versions track by version._id" ng-class="{\'version-auto\': version.type === \'auto\', \'version-milestone\': version.type === \'milestone\', \'version-manual\': version.type === \'manual\', \'version-publish\': version.type === \'publish\', \'version-legacy\': version.type === \'legacy\', \'selected\': $ctrl.selectedVersion && $ctrl.selectedVersion._id === version._id }" ng-click="$ctrl.previewVersion(version)" > <div class="version-icon"> <i class="fa" ng-class="version.typeIcon"></i> </div> <div class="version-info"> <div class="version-header"> <span class="version-type">{{ version.name || version.typeLabel }}</span> <span class="version-number" ng-if="version.versionNumber" >v{{ version.versionNumber }}</span > </div> <div class="version-meta"> <span class="version-date">{{ version.formattedDate }}</span> <span class="version-author" ng-if="version.createdByName && version.createdByName !== \'Система\'" > — {{ version.createdByName }} </span> </div> <div class="version-change" ng-if="version.changePercentFormatted"> <span class="change-label">{{ \'Изменения:\' | t }}</span> {{ version.changePercentFormatted }} </div> </div> <div class="version-actions" ng-click="$event.stopPropagation()"> <button type="button" class="version-action-btn restore" ng-click="$ctrl.restoreVersion(version)" title="{{ \'Восстановить эту версию\' | t }}" > <i class="fa fa-undo"></i> </button> <button type="button" class="version-action-btn delete" ng-if="version.type === \'manual\'" ng-click="$ctrl.deleteVersion(version)" title="{{ \'Удалить версию\' | t }}" >
1 <i class="fa fa-trash"></i> </button> </div> </div> </div> <div class="version-preview" ng-if="$ctrl.selectedVersion"> <div class="version-preview-header"> <h4> <span ng-if="$ctrl.selectedVersion.versionNumber" >{{ \'Версия\' | t }} {{ $ctrl.selectedVersion.versionNumber }}</span > <span ng-if="!$ctrl.selectedVersion.versionNumber" >{{ $ctrl.selectedVersion.typeLabel }}</span > <span class="version-type-badge" ng-if="$ctrl.selectedVersion.versionNumber" >{{ $ctrl.selectedVersion.typeLabel }}</span > </h4> <button type="button" class="close-btn" ng-click="$ctrl.closeVersionPreview()" title="{{ \'Закрыть\' | t }}" > <i class="fa fa-times"></i> </button> </div> <div class="version-preview-meta"> <span>{{ $ctrl.selectedVersion.formattedDate }}</span> <span ng-if="$ctrl.selectedVersion.name"> — {{ $ctrl.selectedVersion.name }}</span> </div> <div class="version-preview-actions"> <button type="button" class="ds-btn btn-compare" ng-click="$ctrl.openDiffPopup()"> <i class="fa fa-columns"></i> {{ \'Сравнить\' | t }} </button> <button type="button" class="ds-btn ds-btn-primary" ng-click="$ctrl.restoreVersion($ctrl.selectedVersion)" > <i class="fa fa-undo"></i> {{ \'Восстановить\' | t }} </button> </div> <div class="version-view-toggle" role="group" aria-label="{{ \'Вид версии\' | t }}" ng-if="$ctrl.versionViews.hasMarks" > <button type="button" class="version-view-toggle__btn" ng-class="{ \'is-active\': $ctrl.versionView === \'clean\' }" aria-pressed="{{ $ctrl.versionView === \'clean\' }}" ng-click="$ctrl.setVersionView(\'clean\')" > {{ \'Без правок\' | t }} </button> <button type="button" class="version-view-toggle__btn" ng-class="{ \'is-active\': $ctrl.versionView === \'marks\' }" aria-pressed="{{ $ctrl.versionView === \'marks\' }}" ng-click="$ctrl.setVersionView(\'marks\')" > {{ \'С правками\' | t }} </button> </div> <div class="version-preview-content" ng-class="{ \'version-preview-content--marks\': $ctrl.isVersionMarksView() }" ng-bind-html="$ctrl.getVersionPreviewHtml() | trustedHtml" ></div> </div> </div> <div class="version-diff-popup-backdrop" ng-if="$ctrl.diffPopupVisible" ng-click="$ctrl.closeDiffPopup()" aria-hidden="true" ></div> <div class="version-diff-popup" ng-if="$ctrl.diffPopupVisible" role="dialog" aria-modal="true" aria-labelledby="version-diff-popup-title" tabindex="-1" > <div class="version-diff-popup-header"> <h3 id="version-diff-popup-title">{{ \'Сравнение версий\' | t }}</h3> <button type="button" class="close-btn" ng-click="$ctrl.closeDiffPopup()" title="{{ \'Закрыть\' | t }}" aria-label="{{ \'Закрыть окно сравнения версий\' | t }}" > <i class="fa fa-times"></i> </button> </div> <div class="version-diff-loading" ng-if="$ctrl.diffLoading"> <i class="fa fa-spinner fa-spin"></i> {{ \'Вычисление различий...\' | t }} </div> <div class="version-diff-popup-body" ng-if="!$ctrl.diffLoading && $ctrl.diffHtml"> <div class="version-diff-side"> <div class="version-diff-side-header"> <div class="version-dropdown version-dropdown--left"> <button type="button" class="version-dropdown__trigger" ng-click="$ctrl.toggleDiffDropdown(\'left\'); $event.stopPropagation()" ng-class="{\'version-dropdown__trigger--open\': $ctrl.diffDropdownOpen === \'left\' }" > <span class="version-dropdown__label" >{{ $ctrl.getDiffVersionLabel($ctrl.diffLeftVersionId) }}</span > <i class="fa fa-chevron-down version-dropdown__arrow"></i> </button> <div class="version-dropdown__menu" ng-if="$ctrl.diffDropdownOpen === \'left\'"> <div class="version-dropdown__item" ng-class="{\'version-dropdown__item--active\': $ctrl.diffLeftVersionId === \'__current__\' }" ng-click="$ctrl.selectDiffVersion(\'left\', \'__current__\')" > <i class="fa fa-file-alt version-dropdown__item-icon"></i> <span>{{ \'Текущий текст\' | t }}</span> </div> <div class="version-dropdown__item" ng-repeat="v in $ctrl.versions track by v._id" ng-class="{\'version-dropdown__item--active\': $ctrl.diffLeftVersionId === v._id }" ng-click="$ctrl.selectDiffVersion(\'left\', v._id)" > <i class="fa version-dropdown__item-icon" ng-class="v.typeIcon"></i> <span>{{ v.name || v.formattedDate }}</span> <span class="version-dropdown__item-badge" ng-if="v.versionNumber" >v{{ v.versionNumber }}</span > </div> </div> </div> </div> <div class="version-diff-side-content" ng-bind-html="$ctrl.diffLeftHtml | trustedHtml" ></div> </div> <div class="version-diff-side version-diff-side--current"> <div class="version-diff-side-header"> <div class="version-dropdown version-dropdown--right"> <button type="button" class="version-dropdown__trigger" ng-click="$ctrl.toggleDiffDropdown(\'right\'); $event.stopPropagation()" ng-class="{\'version-dropdown__trigger--open\': $ctrl.diffDropdownOpen === \'right\' }" > <span class="version-dropdown__label" >{{ $ctrl.getDiffVersionLabel($ctrl.diffRightVersionId) }}</span > <i class="fa fa-chevron-down version-dropdown__arrow"></i> </button> <div class="version-dropdown__menu" ng-if="$ctrl.diffDropdownOpen === \'right\'"> <div class="version-dropdown__item" ng-class="{\'version-dropdown__item--active\': $ctrl.diffRightVersionId === \'__current__\' }" ng-click="$ctrl.selectDiffVersion(\'right\', \'__current__\')" > <i class="fa fa-file-alt version-dropdown__item-icon"></i> <span>{{ \'Текущий текст\' | t }}</span> </div> <div class="version-dropdown__item" ng-repeat="v in $ctrl.versions track by v._id" ng-class="{\'version-dropdown__item--active\': $ctrl.diffRightVersionId === v._id }" ng-click="$ctrl.selectDiffVersion(\'right\', v._id)" > <i class="fa version-dropdown__item-icon" ng-class="v.typeIcon"></i> <span>{{ v.name || v.formattedDate }}</span> <span class="version-dropdown__item-badge" ng-if="v.versionNumber" >v{{ v.versionNumber }}</span > </div> </div> </div> <span class="version-diff-side-meta version-diff-legend-inline"> <ins>{{ \'добавлено\' | t }}</ins> <del>{{ \'удалено\' | t }}</del> </span> </div> <div class="version-diff-scroll-wrapper"> <div class="version-diff-side-content version-diff-content" ng-bind-html="$ctrl.diffHtml | trustedHtml" ></div> <div class="version-diff-gutter"></div> </div> </div> </div>
1 <div class="version-diff-empty" ng-if="!$ctrl.diffLoading && !$ctrl.diffHtml"> <i class="fa fa-check-circle"></i> <p>{{ $ctrl.diffEmptyMessage || (\'Версии идентичны\' | t) }}</p> <span class="hint" >{{ $ctrl.diffEmptyHint || (\'Выбранные версии не содержат различий\' | t) }}</span > </div> </div> <div class="tiptap-dialog-backdrop" ng-if="$ctrl.versionNameDialogVisible" ng-click="$ctrl.closeVersionNameDialog()" ></div> <div class="tiptap-dialog tiptap-version-dialog" ng-if="$ctrl.versionNameDialogVisible"> <div class="tiptap-dialog__header"> <div class="tiptap-dialog__title">{{ \'Сохранить версию\' | t }}</div> <button type="button" class="tiptap-dialog__close" ng-click="$ctrl.closeVersionNameDialog()" aria-label="{{ \'Закрыть\' | t }}" > &times; </button> </div> <div class="tiptap-dialog__input-row"> <input type="text" class="tiptap-dialog__input" placeholder="{{ $ctrl.versionNamePlaceholder }}" ng-model="$ctrl.versionNameInput" ng-keydown="$event.keyCode === 13 && $ctrl.confirmSaveVersion(); $event.keyCode === 27 && $ctrl.closeVersionNameDialog()" autofocus /> </div> <div class="tiptap-dialog__hint"> {{ \'Оставьте пустым для использования значения по умолчанию\' | t }} </div> <div class="tiptap-dialog__buttons"> <button type="button" class="tiptap-dialog__btn tiptap-dialog__btn--secondary" ng-click="$ctrl.closeVersionNameDialog()" > {{ \'Отмена\' | t }} </button> <button type="button" class="tiptap-dialog__btn tiptap-dialog__btn--primary ng-animate-disabled" ng-click="$ctrl.confirmSaveVersion()" ng-disabled="$ctrl.savingVersion" > <span ng-if="!$ctrl.savingVersion">{{ \'Сохранить\' | t }}</span> <span ng-if="$ctrl.savingVersion"><i class="fa fa-spinner fa-spin"></i></span> </button> </div> </div> </form> </div>'),s.put("/pages/edit/editContentItem/partials/collab-capability-notice.html",'<p class="collab-capability-notice" ng-if="$ctrl.collabCommentStoreUnavailable" role="status"> {{ \'Комментарии и обсуждения правок в этом сообществе пока недоступны\' | t }} </p>'),s.put("/pages/edit/editContentItem/partials/comment-filter.html",'<div class="sidebar-filter" ng-if="$ctrl.comments.length" ng-click="$event.stopPropagation()"> <button type="button" class="sidebar-filter-btn" ng-class="{ open: $ctrl.filterPopupOpen, filtered: $ctrl.isCommentFilterModified() }" ng-click="$ctrl.toggleFilterPopup($event)" aria-haspopup="true" aria-controls="comment-filter-popup" ng-attr-aria-expanded="{{ $ctrl.filterPopupOpen ? \'true\' : \'false\' }}" aria-label="{{ \'Фильтры комментариев\' | t }}" title="{{ \'Фильтры\' | t }}" > <i class="fas fa-filter" aria-hidden="true"></i> <span class="sidebar-filter-dot" ng-if="$ctrl.isCommentFilterModified()" aria-hidden="true" ></span> </button> <div id="comment-filter-popup" class="sidebar-filter-popup" ng-if="$ctrl.filterPopupOpen" role="group" aria-label="{{ \'Фильтры комментариев\' | t }}" > <div class="filter-group-label" id="filter-status-label">{{ \'Статус\' | t }}</div> <div class="comment-filter" role="group" aria-labelledby="filter-status-label"> <button type="button" class="comment-filter__btn" ng-class="{ active: $ctrl.commentFilter === \'open\' }" ng-attr-aria-pressed="{{ $ctrl.commentFilter === \'open\' }}" ng-click="$ctrl.setCommentFilter(\'open\')" > {{ \'Открытые\' | t }} </button> <button type="button" class="comment-filter__btn" ng-class="{ active: $ctrl.commentFilter === \'resolved\' }" ng-attr-aria-pressed="{{ $ctrl.commentFilter === \'resolved\' }}" ng-click="$ctrl.setCommentFilter(\'resolved\')" > {{ \'Закрытые\' | t }} </button> </div> <div class="filter-group-label" id="filter-source-label">{{ \'Источник\' | t }}</div> <div class="comment-filter comment-filter--type" role="group" aria-labelledby="filter-source-label" > <button type="button" class="comment-filter__btn" ng-class="{ active: $ctrl.remarkFilter === \'all\' }" ng-attr-aria-pressed="{{ $ctrl.remarkFilter === \'all\' }}" ng-click="$ctrl.setRemarkFilter(\'all\')" > {{ \'Все\' | t }} </button>
1 <button type="button" class="comment-filter__btn" ng-class="{ active: $ctrl.remarkFilter === \'readers\' }" ng-attr-aria-pressed="{{ $ctrl.remarkFilter === \'readers\' }}" ng-click="$ctrl.setRemarkFilter(\'readers\')" > {{ \'От читателей\' | t }} </button> <button type="button" class="comment-filter__btn" ng-class="{ active: $ctrl.remarkFilter === \'editors\' }" ng-attr-aria-pressed="{{ $ctrl.remarkFilter === \'editors\' }}" ng-click="$ctrl.setRemarkFilter(\'editors\')" > {{ \'Редакторские\' | t }} </button> </div> </div> </div>'),s.put("/pages/edit/editContentItem/partials/comment-list.html",'<div id="collab-recovery-list" class="collab-capability-notice collab-recovery-list" ng-if="$ctrl.commentRecoveryDrafts.length" > <p id="collab-recovery-status" role="status" aria-live="polite" aria-atomic="true" tabindex="-1"> {{ \'Неотправленный текст сохранён ниже. Скопируйте его перед выходом со страницы\' | t }} </p> <div class="collab-recovery-draft" ng-repeat="draft in $ctrl.commentRecoveryDrafts track by draft.id" > <label for="collab-recovery-{{ ::$index }}">{{ draft.label | t }}</label> <textarea id="collab-recovery-{{ ::$index }}" readonly rows="3" ng-model="draft.text" ></textarea> </div> </div> <div class="sidebar-empty" ng-if="$ctrl.comments.length && !$ctrl.filteredComments.length"> <i class="fas fa-filter"></i> <p> {{ \'Нет\' | t }} {{ $ctrl.commentFilter === \'resolved\' ? (\'закрытых\' | t) : (\'открытых\' | t) }} {{ \'ремарок\' | t }}{{ $ctrl.remarkFilter === \'readers\' ? \' \' + (\'от читателей\' | t) : ($ctrl.remarkFilter === \'editors\' ? \' \' + (\'от редакторов\' | t) : \'\') }} </p> </div> <div class="comment-list ng-animate-disabled" ng-if="$ctrl.filteredComments.length" data-collab-comments-region tabindex="-1" > <div class="comment-item" ng-repeat="comment in $ctrl.filteredComments | limitTo:$ctrl.commentsDisplayLimit track by comment.id" ng-class="{ resolved: comment.resolved, rejected: comment.rejected, orphaned: comment.orphaned, expanded: comment.isExpanded }" data-sidebar-comment-id="{{ ::comment.id }}" ng-click="$ctrl.toggleCommentExpand(comment)" role="group" ng-attr-aria-label="{{ ::(\'Комментарий от\' | t) + \' \' + (comment.authorName || (\'Автор\' | t)) }}" > <div class="comment-header"> <div class="comment-header-main"> <a class="comment-author" ng-href="/{{ ::comment.authorSlug }}" ng-if="::comment.authorSlug" target="_blank" rel="noopener noreferrer" ng-click="$event.stopPropagation()" > <img class="author-avatar" ng-if="::comment.authorAvatar" ng-src="{{ ::comment.authorAvatar }}" alt="" width="28" height="28" loading="lazy" decoding="async" ng-on-error="$event.target.style.display=\'none\'" /> <span class="author-avatar-placeholder" ng-if="::(!comment.authorAvatar && comment.authorId && comment.authorId !== \'anonymous\')" title="{{ ::comment.authorName || (\'Автор\' | t) }}" >{{ ::(comment.authorName || (\'А\' | t)).charAt(0) }}</span > <span class="author-avatar-placeholder author-avatar-placeholder--anon" ng-if="::(!comment.authorAvatar && (!comment.authorId || comment.authorId === \'anonymous\'))" title="{{ \'Анонимно\' | t }}" ><i class="fas fa-user-secret" aria-hidden="true"></i ></span> <div class="sidebar-author-info"> <span class="author-name">{{ ::comment.authorName || (\'Автор\' | t) }}</span> <span class="sidebar-date">{{ ::comment.createdAt | date:\'dd.MM HH:mm\' }}</span> </div> </a> <span class="comment-author" ng-if="::(!comment.authorSlug)"> <img class="author-avatar" ng-if="::comment.authorAvatar" ng-src="{{ ::comment.authorAvatar }}" alt="" width="28" height="28" loading="lazy" decoding="async" ng-on-error="$event.target.style.display=\'none\'" /> <span class="author-avatar-placeholder" ng-if="::(!comment.authorAvatar && comment.authorId && comment.authorId !== \'anonymous\')" title="{{ ::comment.authorName || (\'Автор\' | t) }}" >{{ ::(comment.authorName || (\'А\' | t)).charAt(0) }}</span > <span class="author-avatar-placeholder author-avatar-placeholder--anon" ng-if="::(!comment.authorAvatar && (!comment.authorId || comment.authorId === \'anonymous\'))" title="{{ \'Анонимно\' | t }}" ><i class="fas fa-user-secret" aria-hidden="true"></i ></span> <div class="sidebar-author-info"> <span class="author-name" >{{ ::(!comment.authorId || comment.authorId === \'anonymous\') ? (\'Анонимно\' | t) : (comment.authorName || (\'Автор\' | t)) }}</span > <span class="sidebar-date">{{ ::comment.createdAt | date:\'dd.MM HH:mm\' }}</span> </div> </span> <span class="remark-badge" ng-if="::comment.isRemark">{{ \'ремарка\' | t }}</span> </div> <div class="comment-actions" ng-class="{\'has-open-menu\': comment.menuOpen}" ng-click="$event.stopPropagation()" > <button type="button" class="ds-btn-link btn-icon card-disclosure" ng-click="$event.stopPropagation(); $ctrl.toggleCommentDisclosure(comment)" ng-attr-aria-expanded="{{ comment.isExpanded ? \'true\' : \'false\' }}" ng-attr-aria-controls="{{ comment.isExpanded ? (\'comment-discussion-\' + comment.id) : undefined }}" aria-label="{{ (comment.isExpanded ? \'Свернуть комментарий\' : \'Развернуть комментарий\') | t }}" > <i class="fas fa-chevron-down" aria-hidden="true"></i> </button> <span class="ds-btn-link btn-icon orphaned-hint" ng-if="comment.orphaned" aria-label="{{ ::(comment.isRemark ? (\'Ремарка читателя — фрагмент не привязан к тексту\' | t) : (\'Комментарий не привязан к тексту\' | t)) }}" > <i class="fas fa-unlink"></i> <span class="custom-tooltip" >{{ ::(comment.isRemark ? (\'Не привязана к тексту\' | t) : (\'Не привязан к тексту\' | t)) }}</span > </span> <button type="button" class="ds-btn-link btn-icon ds-btn-success" ng-click="$ctrl.resolve
1Comment(comment)" ng-if="!comment.resolved && !comment.rejected && $ctrl.canModerate()" ng-disabled="$ctrl.isCommentWriteBlocked(comment)" aria-label="{{ \'Принять\' | t }}" > <i class="fas fa-check"></i> <span class="custom-tooltip">{{ \'Принять\' | t }}</span> </button> <button type="button" class="ds-btn-link btn-icon" ng-click="$ctrl.unresolveComment(comment)" ng-if="comment.resolved && !comment.rejected && $ctrl.canModerate()" ng-disabled="$ctrl.isCommentWriteBlocked(comment)" aria-label="{{ \'Открыть\' | t }}" > <i class="fas fa-undo"></i> <span class="custom-tooltip">{{ \'Открыть\' | t }}</span> </button> <button type="button" class="ds-btn-link btn-icon ds-btn-warning" ng-click="$ctrl.rejectComment(comment)" ng-if="!comment.resolved && !comment.rejected && $ctrl.canModerate()" ng-disabled="$ctrl.isCommentWriteBlocked(comment)" data-collab-reject-id="{{ ::comment.id }}" data-collab-reject-surface="sidebar" aria-label="{{ \'Отклонить\' | t }}" > <i class="fas fa-times"></i> <span class="custom-tooltip">{{ \'Отклонить\' | t }}</span> </button> <div class="item-actions-menu" ng-if="$ctrl.canEditComment(comment) || $ctrl.canDeleteComment(comment)" > <button type="button" class="ds-btn-link btn-icon item-menu-trigger" ng-click="$ctrl.toggleItemMenu(comment, $event)" ng-disabled="$ctrl.isCommentWriteBlocked(comment)" aria-label="{{ \'Действия с комментарием\' | t }}" > <i class="fas fa-ellipsis-v"></i> </button> <div class="item-dropdown" ng-if="comment.menuOpen" ng-click="$event.stopPropagation()"> <button type="button" class="item-dropdown-btn" ng-if="$ctrl.canEditComment(comment) && !comment.resolved && !comment.rejected && !comment.isEditing" ng-click="$ctrl.startEditComment(comment); comment.menuOpen = false" ng-disabled="$ctrl.isCommentWriteBlocked(comment)" > {{ \'Редактировать\' | t }} </button> <button type="button" class="item-dropdown-btn item-dropdown-btn--danger" ng-if="$ctrl.canDeleteComment(comment)" ng-click="$ctrl.deleteComment(comment); comment.menuOpen = false" ng-disabled="$ctrl.isCommentWriteBlocked(comment)" > {{ \'Удалить\' | t }} </button> </div> </div> </div> </div> <p class="collab-write-uncertain" ng-if="$ctrl.isCommentWriteUncertain(comment)" role="status"> <i class="fas fa-exclamation-triangle" aria-hidden="true"></i> {{ \'Не удалось подтвердить изменение. Обновите страницу, прежде чем продолжить\' | t }} </p> <div class="comment-quoted" ng-if="::(comment.quotedText && comment.quotedText.trim())"> <i>&laquo;{{ ::comment.quotedText }}&raquo;</i> </div> <div class="comment-text" ng-if="!comment.isEditing" ng-bind-html="comment.text | inlineMarkdown | mentionLinksHtml" ></div> <div class="comment-edit-form" ng-if="comment.isEditing" ng-click="$event.stopPropagation()"> <textarea class="comment-edit-textarea" ng-model="comment.editText" mention-autocomplete rich-text-shortcuts rows="2" msd-elastic="msd-elastic" aria-label="{{ \'Редактирование комментария\' | t }}" ng-keydown="$ctrl.onCommentEditKeydown($event, comment)" ng-readonly="$ctrl.isCommentWriteBlocked(comment)" ></textarea> <div class="comment-edit-actions"> <button type="button" class="btn-edit-cancel" ng-click="$ctrl.cancelEditComment(comment)" ng-disabled="$ctrl.isCommentWritePending(comment)" > {{ \'Отменить\' | t }} </button> <button type="button" class="btn-edit-save" ng-click="$ctrl.saveEditComment(comment)" ng-disabled="!comment.editText || !comment.editText.trim() || $ctrl.isCommentWriteBlocked(comment)" > {{ \'Сохранить\' | t }} </button> </div> </div> <div class="replies-count-hint" ng-if="!comment.isExpanded && comment.replies && comment.replies.length" > <i class="fas fa-reply" aria-hidden="true"></i> <span>{{ comment.replies.length | pluralize:\'ответ\':\'ответа\':\'ответов\' }}</span> </div> <div id="comment-discussion-{{ ::comment.id }}" ng-if="comment.isExpanded" ng-click="$event.stopPropagation()" ng-keydown="($event.key === \'Enter\' || $event.key === \' \') && $event.stopPropagation()" > <ng-include src="\'/pages/edit/editContentItem/partials/comment-replies.html\'"></ng-include> </div> </div>
1 <button type="button" class="sidebar-show-more" ng-if="$ctrl.filteredComments.length > $ctrl.commentsDisplayLimit" ng-click="$ctrl.showMoreComments()" > {{ \'Показать ещё\' | t }} {{ $ctrl.filteredComments.length - $ctrl.commentsDisplayLimit }} </button> </div>'),s.put("/pages/edit/editContentItem/partials/comment-replies.html",'<div class="comment-replies" ng-if="comment.replies && comment.replies.length > 0" role="list" aria-label="{{ \'Ответы на комментарий\' | t }}" > <div class="reply-item" ng-repeat="reply in comment.replies track by reply._id" role="listitem" data-reply-id="{{ ::reply._id }}" > <div class="reply-header"> <a class="reply-author" ng-href="/{{ ::reply.author.path }}" ng-if="::(reply.author && reply.author.path)" target="_blank" rel="noopener noreferrer" > <img class="author-avatar" ng-if="::reply.authorAvatar" ng-src="{{ ::reply.authorAvatar }}" alt="" ng-on-error="$event.target.style.display=\'none\'" /> <span class="author-avatar-placeholder" ng-if="::(!reply.authorAvatar)" title="{{ ::(reply.author && reply.author.name) || (\'Автор\' | t) }}" >{{ ::((reply.author && reply.author.name) || (\'А\' | t)).charAt(0) }}</span > <div class="sidebar-author-info"> <span class="author-name" >{{ ::(reply.author && reply.author.name) || (\'Автор\' | t) }}</span > <span class="sidebar-date">{{ ::reply.createdAt | date:\'dd.MM HH:mm\' }}</span> </div> </a> <span class="reply-author" ng-if="::(!reply.author || !reply.author.path)"> <img class="author-avatar" ng-if="::reply.authorAvatar" ng-src="{{ ::reply.authorAvatar }}" alt="" ng-on-error="$event.target.style.display=\'none\'" /> <span class="author-avatar-placeholder" ng-if="::(!reply.authorAvatar)" title="{{ ::(reply.author && reply.author.name) || (\'А\' | t) }}" >{{ ::((reply.author && reply.author.name) || (\'А\' | t)).charAt(0) }}</span > <div class="sidebar-author-info"> <span class="author-name" >{{ ::(reply.author && reply.author.name) || (\'Автор\' | t) }}</span > <span class="sidebar-date">{{ ::reply.createdAt | date:\'dd.MM HH:mm\' }}</span> </div> </span> <div class="item-actions-menu" ng-if="$ctrl.canEditReply(reply) || $ctrl.canDeleteReply(reply)" ng-class="{\'has-open-menu\': reply.menuOpen}" ng-click="$event.stopPropagation()" > <button type="button" class="ds-btn-link btn-icon item-menu-trigger reply-menu-trigger" ng-click="$ctrl.toggleItemMenu(reply, $event)" ng-disabled="$ctrl.isCommentWriteBlocked(comment) || $ctrl.isReplyWriteBlocked(reply)" aria-label="{{ \'Действия с ответом\' | t }}" > <i class="fas fa-ellipsis-v"></i> </button> <div class="item-dropdown" ng-if="reply.menuOpen" ng-click="$event.stopPropagation()"> <button type="button" class="item-dropdown-btn" ng-if="$ctrl.canEditReply(reply) && !reply.isEditing" ng-click="$ctrl.startEditReply(reply); reply.menuOpen = false" ng-disabled="$ctrl.isCommentWriteBlocked(comment) || $ctrl.isReplyWriteBlocked(reply)" > {{ \'Редактировать\' | t }} </button> <button type="button" class="item-dropdown-btn item-dropdown-btn--danger" ng-if="$ctrl.canDeleteReply(reply)" ng-click="$ctrl.deleteReply(comment, reply); reply.menuOpen = false" ng-disabled="$ctrl.isCommentWriteBlocked(comment) || $ctrl.isReplyWriteBlocked(reply)" > {{ \'Удалить\' | t }} </button> </div> </div> </div> <p class="collab-write-uncertain" ng-if="$ctrl.isReplyWriteUncertain(reply)" role="status"> <i class="fas fa-exclamation-triangle" aria-hidden="true"></i> {{ \'Не удалось подтвердить изменение. Обновите страницу, прежде чем продолжить\' | t }} </p> <div class="reply-text" ng-if="!reply.isEditing" ng-bind-html="reply.text | inlineMarkdown | mentionLinksHtml" ></div> <div class="reply-edit-form" ng-if="reply.isEditing"> <textarea class="reply-edit-textarea" ng-model="reply.editText" mention-autocomplete rich-text-shortcuts rows="1" msd-elastic="msd-elastic" aria-label="{{ \'Редактирование ответа\' | t }}" ng-keydown="$ctrl.onReplyEditKeydown($event, comment, reply)" ng-readonly="$ctrl.isCommentWriteBlocked(comment) || $ctrl.isReplyWriteBlocked(reply)" ></textarea> <div class="reply-edit-actions"> <button type="button" class="btn-edit-cancel" ng-click="$ctrl.cancelEditReply(reply)" ng-disabled="$ctrl.isReplyWritePending(reply)" > {{ \'Отменить\' | t }} </button> <button type="button" class="btn-edit-save" ng-click="$ctrl.saveEditReply(comment, reply)" ng-disabled="!reply.editText || !reply.editText.trim() || $ctrl.isCommentWriteBlocked(comment) || $ctrl.isReplyWriteBlocked(reply)" > {{ \'Сохранить\' | t }} </button> </div> </div> </div> </div> <div class="comment-add-reply" ng-if="!comment.resolved"> <div class="reply-input-wrapper">
1 <textarea ng-model="comment.newReplyText" placeholder="{{ \'Ответить...\' | t }}" aria-label="{{ \'Текст ответа на комментарий\' | t }}" mention-autocomplete rich-text-shortcuts rows="1" msd-elastic="msd-elastic" ng-keydown="$ctrl.onReplyKeydown($event, comment)" ng-readonly="$ctrl.isCommentWriteBlocked(comment)" ></textarea> <button type="button" class="reply-send-btn btn-icon" ng-click="$ctrl.addReply(comment)" ng-disabled="!comment.newReplyText || !comment.newReplyText.trim() || $ctrl.isCommentWriteBlocked(comment)" aria-label="{{ \'Отправить ответ\' | t }}" > <i class="fas fa-paper-plane" aria-hidden="true"></i> <span class="custom-tooltip">{{ \'Отправить\' | t }}</span> </button> </div> </div>'),s.put("/pages/edit/editContentItem/partials/cover-actions-mobile.html",'<div class="cover-actions-mobile"> <button type="button" class="cover-actions-toggle" aria-label="{{ \'Действия с обложкой\' | t }}" aria-haspopup="true" ng-attr-aria-expanded="{{$ctrl.coverMenuOpen}}" ng-click="$ctrl.toggleCoverMenu($event)" ng-disabled="$ctrl.cropUploading || $ctrl.imageUploading[\'root\']" > <i class="fas fa-ellipsis-v" aria-hidden="true"></i> </button> <div class="cover-actions-dropdown" role="menu" ng-show="$ctrl.coverMenuOpen" ng-click="$event.stopPropagation()" > <button type="button" role="menuitem" class="cover-action-item" ng-click="$ctrl.cropCoverImage(); $ctrl.coverMenuOpen = false" ng-disabled="$ctrl.cropUploading" aria-label="{{ \'Кадрировать обложку\' | t }}" > <i class="fas fa-crop-alt" aria-hidden="true"></i> <span>{{ \'Кадрировать\' | t }}</span> </button> <button type="button" role="menuitem" class="cover-action-item" ng-click="$ctrl.coverMenuOpen = false" ngf-select="$ctrl.uploadImage($file)" ngf-accept="\'image/*\'" ng-disabled="$ctrl.cropUploading || $ctrl.imageUploading[\'root\']" aria-label="{{ \'Заменить обложку\' | t }}" > <i class="fas fa-image" aria-hidden="true"></i> <span>{{ \'Заменить\' | t }}</span> </button> <button type="button" role="menuitem" class="cover-action-item cover-action-item--delete" ng-click="$ctrl.deleteCoverImage(); $ctrl.coverMenuOpen = false" ng-disabled="$ctrl.cropUploading" aria-label="{{ \'Удалить обложку\' | t }}" > <i class="fas fa-times" aria-hidden="true"></i> <span>{{ \'Удалить\' | t }}</span> </button> </div> </div>'),s.put("/pages/edit/editContentItem/partials/expo-preview.html",'<div class="expo-preview-wrapper tiptap-preview-mode" ng-if="$ctrl.isPreviewMode && $ctrl.previewPost" ng-init="editCtrl = $ctrl; post = $ctrl.previewPost" > <div class="button-container"> <button type="button" class="preview-close-button" ng-click="editCtrl.togglePreviewMode()"> <svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg" > <path d="M17.9975 4.89363L13.1038 0L0 13.1064V18H4.89363L17.9975 4.89363ZM13.105 2.55674L15.4421 4.89383L14.4612 5.87591L12.1241 3.53882L13.105 2.55674ZM10.8453 4.81648L13.1824 7.15357L5.42174 14.9137L3.08485 12.5756L10.8453 4.81648ZM1.80725 16.1915V13.8544L4.14434 16.1915H1.80725Z" fill="currentColor" /> </svg> {{ \'Продолжить редактировать\' | t }} </button> </div> <div ng-controller="ExpoPreviewCtrl as $ctrl"> <ng-include src="\'/pages/expo/expoMainPart.html\'"></ng-include> </div> <div class="button-container bottom-button" ng-if="editCtrl.showBottomEditButton"> <button type="button" class="preview-close-button bottom" ng-click="editCtrl.togglePreviewMode()" > <svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg" > <path d="M17.9975 4.89363L13.1038 0L0 13.1064V18H4.89363L17.9975 4.89363ZM13.105 2.55674L15.4421 4.89383L14.4612 5.87591L12.1241 3.53882L13.105 2.55674ZM10.8453 4.81648L13.1824 7.15357L5.42174 14.9137L3.08485 12.5756L10.8453 4.81648ZM1.80725 16.1915V13.8544L4.14434 16.1915H1.80725Z" fill="currentColor" /> </svg> {{ \'Продолжить редактировать\' | t }} </button> </div> </div>'),s.put("/pages/edit/editContentItem/partials/margin-view.html",'<div class="margin-items-column" ng-show="$ctrl.marginViewMode" aria-label="{{ \'Ремарки и правки на полях\' | t }}
1" aria-live="polite" aria-atomic="false" data-collab-margin-region tabindex="-1" > <div class="margin-item-card" ng-repeat="comment in $ctrl.visibleMarginComments track by comment.id" ng-class="{ resolved: comment.resolved, remark: comment.isRemark, orphaned: comment.orphaned,\'margin-expanded\': comment.marginExpanded }" ng-attr-title="{{ comment.orphaned ? (comment.isRemark ? (\'Ремарка больше не привязана к тексту\'|t) : (\'Комментарий больше не привязан к тексту\'|t)) : undefined }}" data-margin-comment-id="{{ ::comment.id }}" ng-click="$ctrl.toggleMarginCard(comment, $event)" role="group" ng-attr-aria-label="{{ ::(comment.isRemark ? (\'Ремарка от\'|t) : (\'Комментарий от\'|t)) + \' \' + (comment.authorName || (\'Автор\'|t)) }}" > <div class="margin-card-header"> <div class="margin-card-header-main"> <img class="margin-avatar" ng-if="::comment.authorAvatar" ng-src="{{ ::comment.authorAvatar }}" alt="" ng-on-error="$event.target.style.display=\'none\'" /> <span class="margin-avatar-placeholder" ng-if="::(!comment.authorAvatar && comment.authorId && comment.authorId !== \'anonymous\')" >{{ ::(comment.authorName || (\'А\' | t)).charAt(0) }}</span > <span class="margin-avatar-placeholder margin-avatar-placeholder--anon" ng-if="::(!comment.authorAvatar && (!comment.authorId || comment.authorId === \'anonymous\'))" title="{{ \'Анонимно\' | t }}" ><i class="fas fa-user-secret" aria-hidden="true"></i ></span> <div class="margin-author-info"> <a class="margin-author" ng-if="::comment.authorSlug" ng-href="/{{ ::comment.authorSlug }}" target="_blank" rel="noopener noreferrer" ng-click="$event.stopPropagation()" >{{ ::comment.authorName || (\'Автор\' | t) }}</a > <span class="margin-author" ng-if="::(!comment.authorSlug)" >{{ ::(!comment.authorId || comment.authorId === \'anonymous\') ? (\'Анонимно\' | t) : (comment.authorName || (\'Автор\' | t)) }}</span > <span class="margin-date">{{ ::comment.createdAt | date:\'dd.MM HH:mm\' }}</span> </div> </div> <div class="margin-card-header-actions" ng-class="{\'has-open-menu\': comment.menuOpen}" ng-click="$event.stopPropagation()" > <button type="button" class="ds-btn-link btn-icon card-disclosure" ng-click="$event.stopPropagation(); $ctrl.toggleMarginDisclosure(comment)" ng-attr-aria-expanded="{{ comment.marginExpanded ? \'true\' : \'false\' }}" ng-attr-aria-controls="{{ comment.marginExpanded ? (\'margin-comment-discussion-\' + comment.id) : undefined }}" aria-label="{{ (comment.marginExpanded ? \'Свернуть комментарий\' : \'Развернуть комментарий\') | t }}" > <i class="fas fa-chevron-down" aria-hidden="true"></i> </button> <button type="button" class="ds-btn-link btn-icon ds-btn-success" ng-if="!comment.resolved && !comment.rejected && $ctrl.canModerate()" ng-click="$ctrl.resolve
1Comment(comment)" ng-disabled="$ctrl.isCommentWriteBlocked(comment)" aria-label="{{ \'Принять\' | t }}" > <i class="fas fa-check"></i><span class="custom-tooltip">{{ \'Принять\' | t }}</span> </button> <button type="button" class="ds-btn-link btn-icon ds-btn-warning" ng-if="!comment.resolved && !comment.rejected && $ctrl.canModerate()" ng-click="$ctrl.rejectComment(comment)" ng-disabled="$ctrl.isCommentWriteBlocked(comment)" data-collab-reject-id="{{ ::comment.id }}" data-collab-reject-surface="margin" aria-label="{{ \'Отклонить\' | t }}" > <i class="fas fa-times"></i><span class="custom-tooltip">{{ \'Отклонить\' | t }}</span> </button> <div class="item-actions-menu" ng-if="$ctrl.canEditComment(comment) || $ctrl.canDeleteComment(comment)" > <button type="button" class="ds-btn-link btn-icon item-menu-trigger" ng-click="$ctrl.toggleItemMenu(comment, $event)" ng-disabled="$ctrl.isCommentWriteBlocked(comment)" aria-label="{{ \'Действия с комментарием\' | t }}" > <i class="fas fa-ellipsis-v"></i> </button> <div class="item-dropdown" ng-if="comment.menuOpen" ng-click="$event.stopPropagation()"> <button type="button" class="item-dropdown-btn" ng-if="$ctrl.canEditComment(comment) && !comment.resolved && !comment.rejected && !comment.isEditing" ng-click="$ctrl.startEditComment(comment); comment.marginExpanded = true; comment.menuOpen = false" ng-disabled="$ctrl.isCommentWriteBlocked(comment)" > {{ \'Редактировать\' | t }} </button> <button type="button" class="item-dropdown-btn item-dropdown-btn--danger" ng-if="$ctrl.canDeleteComment(comment)" ng-click="$ctrl.deleteComment(comment); comment.menuOpen = false" ng-disabled="$ctrl.isCommentWriteBlocked(comment)" > {{ \'Удалить\' | t }} </button> </div> </div> </div> </div> <div class="margin-card-text" ng-if="!comment.isEditing">{{ comment.text | mentionNames }}</div> <div class="margin-edit-form" ng-if="comment.isEditing" ng-click="$event.stopPropagation()"> <textarea class="margin-edit-textarea" ng-model="comment.editText" mention-autocomplete rich-text-shortcuts rows="2" msd-elastic="msd-elastic" aria-label="{{ \'Редактирование комментария\' | t }}" ng-keydown="$ctrl.onCommentEditKeydown($event, comment)" ng-readonly="$ctrl.isCommentWriteBlocked(comment)" ></textarea> <div class="margin-edit-actions"> <button type="button" class="btn-edit-cancel" ng-click="$ctrl.cancelEditComment(comment)" ng-disabled="$ctrl.isCommentWritePending(comment)" > {{ \'Отменить\' | t }} </button> <button type="button" class="btn-edit-save" ng-click="$ctrl.saveEditComment(comment)" ng-disabled="!comment.editText || !comment.editText.trim() || $ctrl.isCommentWriteBlocked(comment)" > {{ \'Сохранить\' | t }} </button> </div> </div> <p class="collab-write-uncertain" ng-if="$ctrl.isCommentWriteUncertain(comment)" role="status"> <i class="fas fa-exclamation-triangle" aria-hidden="true"></i> {{ $ctrl.suggestionDiscussionNotice(suggestion) }} </p> <div class="margin-card-replies-count" ng-if="comment.replies && comment.replies.length && !comment.marginExpanded" > <i class="fas fa-reply" aria-hidden="true"></i> <span>{{ comment.replies.length | pluralize:\'ответ\':\'ответа\':\'ответов\' }}</span> </div> <div id="margin-comment-discussion-{{ ::comment.id }}" class="margin-disclosure-content" ng-if="comment.marginExpanded" > <div class="margin-card-replies" ng-if="comment.replies && comment.replies.length" ng-click="$event.stopPropagation()" > <div class="margin-reply" ng-repeat="reply in comment.replies track by reply._id" data-reply-id="{{ ::reply._id }}" > <div class="margin-reply-header"> <span class="margin-reply-author" >{{ ::(reply.author && reply.author.name) || \'\' }}</span > <div class="item-actions-menu" ng-if="$ctrl.canEditReply(reply) || $ctrl.canDeleteReply(reply)" ng-class="{\'has-open-menu\': reply.menuOpen}" ng-click="$event.stopPropagation()" > <button type="button" class="ds-btn-link btn-icon item-menu-trigger margin-reply-menu-trigger" ng-click="$ctrl.toggleItemMenu(reply, $event)" ng-disabled="$ctrl.isCommentWriteBlocked(comment) || $ctrl.isReplyWriteBlocked(reply)" aria-label="{{ \'Действия с ответом\' | t }}" > <i class="fas fa-ellipsis-v"></i> </button> <div class="item-dropdown" ng-if="reply.menuOpen" ng-click="$event.stopPropagation()"> <button type="button" class="item-dropdown-btn" ng-if="$ctrl.canEditReply(reply) && !reply.isEditing" ng-click="$ctrl.startEditReply(reply); reply.menuOpen = false" ng-disabled="$ctrl.isCommentWriteBlocked(comment) || $ctrl.isReplyWriteBlocked(reply)" > {{ \'Редактировать\' | t }} </button> <button type="button" class="item-dropdown-btn item-dropdown-btn--danger" ng-if="$ctrl.canDeleteReply(reply)" ng-click="$ctrl.deleteReply(comment, reply); reply.menuOpen = false" ng-disabled="$ctrl.isCommentWriteBlocked(comment) || $ctrl.isReplyWriteBlocked(reply)" > {{ \'Удалить\' | t }} </button> </div> </div> </div> <span class="margin-reply-text" ng-if="!reply.isEditing" ng-bind-html="reply.text | inlineMarkdown | mentionLinksHtml" ></span> <div class="margin-reply-edit-form" ng-if="reply.isEditing"> <textarea class="margin-reply-edit-textarea" ng-model="reply.editText" mention-autocomplete rich-text-shortcuts rows="1" msd-elastic="msd-elastic" aria-label="{{ \'Редактирование ответа\' | t }}" ng-keydown="$ctrl.onReplyEditKeydown($event, comment, reply)" ng-readonly="$ctrl.isCommentWriteBlocked(comment) || $ctrl.isReplyWriteBlocked(reply)" ></textarea> <div class="margin-reply-edit-actions"> <button type="button" class="btn-edit-cancel" ng-click="$ctrl.cancelEditReply(reply)" ng-disabled="$ctrl.isReplyWritePending(reply)" > {{ \'Отменить\' | t }} </button> <button type="button" class="btn-edit-save" ng-click="$ctrl.saveEditReply(comment, reply)" ng-disabled="!reply.editText || !reply.editText.trim() || $ctrl.isCommentWriteBlocked(comment) || $ctrl.isReplyWriteBlocked(reply)" > {{ \'Сохранить\' | t }} </button> </div> </div> <p class="collab-write-uncertain" ng-if="$ctrl.isReplyWriteUncertain(reply)" role="status" > <i class="fas fa-exclamation-triangle" aria-hidden="true"></i> {{ \'Не удалось подтвердить изменение. Обновите страницу, прежде чем продолжить\' | t }} </p> </div> </div> <div class="margin-reply-form" ng-if="$ctrl.userId" ng-click="$event.stopPropagation()" ng-keydown="($event.key === \'Enter\' || $event.key === \' \') && $event.stopPropagation()" >
1 <textarea class="margin-reply-input" ng-model="comment.newReplyText" placeholder="{{ \'Ответить...\' | t }}" aria-label="{{ \'Текст ответа на комментарий\' | t }}" mention-autocomplete rich-text-shortcuts rows="1" msd-elastic="msd-elastic" ng-keydown="$ctrl.onReplyKeydown($event, comment)" ng-readonly="$ctrl.isCommentWriteBlocked(comment)" ></textarea> <button type="button" class="ds-btn-link ds-btn-success margin-reply-submit" ng-mousedown="$event.preventDefault()" ng-click="$event.stopPropagation(); $ctrl.addReply(comment)" ng-disabled="!comment.newReplyText || !comment.newReplyText.trim() || $ctrl.isCommentWriteBlocked(comment)" > {{ \'Ответить\' | t }} </button> </div> </div> </div> <div class="margin-item-card margin-item-card--suggestion" ng-repeat="suggestion in $ctrl.visibleMarginSuggestions track by suggestion.suggestionId" ng-class="[suggestion.type, {\'margin-expanded\': suggestion.marginExpanded }]" data-margin-suggestion-id="{{ ::suggestion.suggestionId }}" ng-click="$ctrl.toggleMarginCard(suggestion, $event)" role="group" ng-attr-aria-label="{{ ::(\'Правка от\'|t) + \' \' + (suggestion.authorName || (\'Автор\'|t)) }}" > <div class="margin-card-header"> <div class="margin-card-header-main"> <img class="margin-avatar" ng-if="::suggestion.authorAvatar" ng-src="{{ ::suggestion.authorAvatar }}" alt="" ng-on-error="$event.target.style.display=\'none\'" /> <span class="margin-avatar-placeholder" ng-if="::(!suggestion.authorAvatar && suggestion.authorId && suggestion.authorId !== \'anonymous\')" >{{ ::(suggestion.authorName || (\'А\' | t)).charAt(0) }}</span > <span class="margin-avatar-placeholder margin-avatar-placeholder--anon" ng-if="::(!suggestion.authorAvatar && (!suggestion.authorId || suggestion.authorId === \'anonymous\'))" title="{{ \'Анонимно\' | t }}" ><i class="fas fa-user-secret" aria-hidden="true"></i ></span> <div class="margin-author-info"> <a class="margin-author" ng-if="::suggestion.authorSlug" ng-href="/{{ ::suggestion.authorSlug }}" target="_blank" rel="noopener noreferrer" ng-click="$event.stopPropagation()" >{{ ::suggestion.authorName || (\'Автор\' | t) }}</a > <span class="margin-author" ng-if="::(!suggestion.authorSlug)" >{{ ::suggestion.authorName || (\'Автор\' | t) }}</span > <span class="margin-date"> {{ ::suggestion.createdAt | date:\'dd.MM HH:mm\' }} <span class="margin-type-badge" ng-if="::(suggestion.type === \'add\' || suggestion.type === \'node-add\')" >+</span > <span class="margin-type-badge margin-type-badge--delete" ng-if="::(suggestion.type === \'delete\' || suggestion.type === \'node-delete\')" >−</span > <span class="margin-type-badge margin-type-badge--replace" ng-if="::suggestion.type === \'replace\'" ><i class="fas fa-exchange-alt"></i ></span> <span class="margin-type-badge margin-type-badge--format" ng-if="::suggestion.type === \'format\'" ><i class="fas fa-paint-brush"></i ></span> <span class="margin-type-badge margin-type-badge--update" ng-if="::suggestion.type === \'node-update\'" ><i class="fas fa-pencil-alt"></i ></span> </span> </div> </div> <div class="margin-card-header-actions" ng-click="$event.stopPropagation()"> <button type="button" class="ds-btn-link btn-icon card-disclosure" ng-click="$event.stopPropagation(); $ctrl.toggleMarginDisclosure(suggestion)" ng-attr-aria-expanded="{{ suggestion.marginExpanded ? \'true\' : \'false\' }}" ng-attr-aria-controls="{{ suggestion.marginExpanded ? (\'margin-suggestion-discussion-\' + suggestion.suggestionId) : undefined }}" aria-label="{{ (suggestion.marginExpanded ? \'Свернуть правку\' : \'Развернуть правку\') | t }}" > <i class="fas fa-chevron-down" aria-hidden="true"></i> </button> <button type="button" class="ds-btn-link btn-icon ds-btn-success" ng-if="$ctrl.canModerate()" ng-click="$ctrl.acceptSuggestion(suggestion)" ng-disabled="$ctrl.isSuggestionActionBlocked(suggestion)" aria-label="{{ \'Принять правку\' | t }}" > <i class="fas fa-check"></i><span class="custom-tooltip">{{ \'Принять\' | t }}</span> </button> <button type="button" class="ds-btn-link btn-icon ds-btn-danger" ng-if="$ctrl.canModerate()" ng-click="$ctrl.rejectSuggestion(suggestion)" ng-disabled="$ctrl.isSuggestionActionBlocked(suggestion)" aria-label="{{ \'Отклонить правку\' | t }}" > <i class="fas fa-times"></i><span class="custom-tooltip">{{ \'Отклонить\' | t }}</span> </button> </div> </div> <p class="collab-write-uncertain" ng-if="$ctrl.suggestionDiscussionNotice(suggestion) && !suggestion.marginExpanded" role="status" > <i class="fas fa-exclamation-triangle" aria-hidden="true"></i> {{ \'Не удалось подтвердить изменение. Обновите страницу, прежде чем продолжить\' | t }} </p> <div class="margin-card-text"> <strong class="suggestion-action-label" ng-if="::(suggestion.type === \'add\' || suggestion.type === \'node-add\')" >{{ \'Добавить:\' | t }}</strong > <strong class="suggestion-action-label" ng-if="::(suggestion.type === \'delete\' || suggestion.type === \'node-delete\')" >{{ \'Удалить:\' | t }}</strong > <strong class="suggestion-action-label" ng-if="::suggestion.type === \'replace\'" >{{ \'Заменить:\' | t }}</strong > <strong class="suggestion-action-label" ng-if="::suggestion.type === \'format\'" >{{ \'Формат:\' | t }}</strong > <strong class="suggestion-action-label" ng-if="::suggestion.type === \'node-update\'" >{{ \'Изменить:\' | t }}</strong > <span ng-if="::suggestion.type === \'add\'" class="text-add">{{ suggestion.text }}</span> <span ng-if="::suggestion.type === \'delete\'" class="text-delete">{{ suggestion.text }}</span> <div ng-if="::suggestion.type === \'replace\'" class="suggestion-replace-block"> <span class="text-delete" >{{ suggestion.deleteText | limitTo:100 }}<span ng-if="suggestion.deleteText.length > 100" >…</span ></span > <span class="text-replace-arrow">→</span> <span class="text-add" >{{ suggestion.addText | limitTo:100 }}<span ng-if="suggestion.addText.length > 100" >…</span ></span > </div> <span ng-if="::suggestion.type === \'format\'" class="text-format" >{{ ::suggestion.formatDescription }}</span > <span ng-if="::(suggestion.type === \'node-add\' || suggestion.type === \'node-delete\' || suggestion.type === \'node-update\')" class="text-format" >{{ ::suggestion.nodeDescription }}</span > <div class="suggestion-update-changes" ng-if="suggestion.updateChanges.length"> <div class="suggestion-update-change" ng-repeat="change in suggestion.updateChanges track by change.key" > <span class="suggestion-update-label">{{ change.label }}</span> <span class="text-delete" ng-if="change.before" >{{ change.before | limitTo:100 }}<span ng-if="change.before.length > 100" >…</span ></span > <span class="text-delete suggestion-update-empty" ng-if="!change.before" >{{ \'пусто\' | t }}</span > <span class="text-replace-arrow">→</span> <span class="text-add" ng-if="change.after" >{{ change.after | limitTo:100 }}<span ng-if="change.after.length > 100">…</span></span > <span class="text-add suggestion-update-empty" ng-if="!change.after" >{{ \'пусто\' | t }}</span > </div> </div> </div> <div class="margin-card-replies-count" ng-if="$ctrl.suggestionThreads[suggestion.suggestionId] && $ctrl.suggestionThreads[suggestion.suggestionId].length && !suggestion.marginExpanded" > <i class="fas fa-reply" aria-hidden="true"></i> <span >{{ $ctrl.suggestionThreads[suggestion.suggestionId].length | pluralize:\'ответ\':\'ответа\':\'ответов\' }}</span > </div> <div id="margin-suggestion-discussion-{{ ::suggestion.suggestionId }}" class="margin-disclosure-content" ng-if="suggestion.marginExpanded" > <p class="collab-write-uncertain" ng-if="$ctrl.suggestionDiscussionNotice(suggestion)" role="status" > <i class="fas fa-exclamation-triangle" aria-hidden="true"></i> {{ $ctrl.suggestionDiscussionNotice(suggestion) }} </p> <div class="margin-card-replies" ng-if="$ctrl.suggestionThreads[suggestion.suggestionId] && $ctrl.suggestionThreads[suggestion.suggestionId].length" ng-click="$event.stopPropagation()" > <div class="margin-reply" ng-repeat="reply in $ctrl.suggestionThreads[suggestion.suggestionId] track by reply._id" data-reply-id="{{ ::reply._id }}" > <div class="margin-reply-header"> <span class="margin-reply-author" >{{ ::(reply.author && reply.author.name) || \'\' }}</span > <div class="item-actions-menu" ng-if="$ctrl.canEditReply(reply) || $ctrl.canDeleteReply(reply)" ng-class="{\'has-open-menu\': reply.menuOpen}" ng-click="$event.stopPropagation()" > <button type="button" class="ds-btn-link btn-icon item-menu-trigger margin-reply-menu-trigger" ng-click="$ctrl.toggleItemMenu(reply, $event)" ng-disabled="$ctrl.isSuggestionReplyWriteBlocked(reply, suggestion)" aria-label="{{ \'Действия с ответом\' | t }}" > <i class="fas fa-ellipsis-v"></i> </button> <div class="item-dropdown" ng-if="reply.menuOpen" ng-click="$event.stopPropagation()"> <button type="button" class="item-dropdown-btn" ng-if="$ctrl.canEditReply(reply) && !reply.isEditing" ng-click="$ctrl.startEditReply(reply, suggestion); reply.menuOpen = false" ng-disabled="$ctrl.isSuggestionReplyWriteBlocked(reply, suggestion)" > {{ \'Редактировать\' | t }} </button> <button type="button" class="item-dropdown-btn item-dropdown-btn--danger" ng-if="$ctrl.canDeleteReply(reply)" ng-click="$ctrl.deleteSuggestionReply(suggestion, reply
1); reply.menuOpen = false" ng-disabled="$ctrl.isSuggestionReplyWriteBlocked(reply, suggestion)" > {{ \'Удалить\' | t }} </button> </div> </div> </div> <span class="margin-reply-text" ng-if="!reply.isEditing" ng-bind-html="reply.text | inlineMarkdown | mentionLinksHtml" ></span> <div class="margin-reply-edit-form" ng-if="reply.isEditing"> <textarea class="margin-reply-edit-textarea" ng-model="reply.editText" mention-autocomplete rich-text-shortcuts rows="1" msd-elastic="msd-elastic" aria-label="{{ \'Редактирование ответа\' | t }}" ng-keydown="$ctrl.onSuggestionReplyEditKeydown($event, suggestion, reply)" ng-readonly="$ctrl.isSuggestionReplyWriteBlocked(reply, suggestion)" ></textarea> <div class="margin-reply-edit-actions"> <button type="button" class="btn-edit-cancel" ng-click="$ctrl.cancelEditReply(reply)" ng-disabled="$ctrl.isSuggestionReplyWritePending(reply)" > {{ \'Отменить\' | t }} </button> <button type="button" class="btn-edit-save" ng-click="$ctrl.saveEditSuggestionReply(suggestion, reply)" ng-disabled="!reply.editText || !reply.editText.trim() || $ctrl.isSuggestionReplyWriteBlocked(reply, suggestion)" > {{ \'Сохранить\' | t }} </button> </div> </div> <p class="collab-write-uncertain" ng-if="$ctrl.isSuggestionReplyWriteUncertain(reply)" role="status" > <i class="fas fa-exclamation-triangle" aria-hidden="true"></i> {{ \'Не удалось подтвердить изменение. Обновите страницу, прежде чем продолжить\' | t }} </p> </div> </div> <div class="margin-reply-form" ng-if="$ctrl.userId" ng-click="$event.stopPropagation()" ng-keydown="($event.key === \'Enter\' || $event.key === \' \') && $event.stopPropagation()" > <textarea class="margin-reply-input" ng-model="suggestion.newReplyText" placeholder="{{ \'Ответить...\' | t }}" aria-label="{{ \'Текст ответа на обсуждение правки\' | t }}" mention-autocomplete rich-text-shortcuts rows="1" msd-elastic="msd-elastic" ng-keydown="$ctrl.onSuggestionReplyKeydown($event, suggestion)" ng-readonly="$ctrl.isSuggestionDiscussionBlocked(suggestion)" ></textarea> <button type="button" class="ds-btn-link ds-btn-success margin-reply-submit" ng-mousedown="$event.preventDefault()" ng-click="$event.stopPropagation(); $ctrl.addSuggestionReply(suggestion)" ng-disabled="!suggestion.newReplyText || !suggestion.newReplyText.trim() || $ctrl.isSuggestionDiscussionBlocked(suggestion)" > {{ \'Ответить\' | t }} </button> </div> </div> </div> </div>'),s.put("/pages/edit/editContentItem/partials/sidebar-side-toggle.html",'<button type="button" class="sidebar-side-toggle" ng-if="contentItem.slug" ng-class="{ \'is-hidden\': $ctrl.sidebarVisible || $ctrl.marginViewMode }" ng-disabled="$ctrl.collabSyncPending" ng-click="$ctrl.toggleSidebar()" ng-attr-aria-pressed="{{ !!$ctrl.sidebarVisible }}" aria-label="{{ \'Панель комментариев и правок\' | t }}" > <svg width="21" height="17" viewBox="0 0 21 17" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M3.88021 0C3.03125 0 2.32031 0.151042 1.74219 0.450522C1.16667 0.75 0.731771 1.1875 0.440104 1.76302C0.145833 2.34115 0 3.04688 0 3.88021V6.52604C0 7.35938 0.145833 8.06511 0.440104 8.64063C0.734375 9.21615 1.16667 9.65625 1.74219 9.95573C2.32031 10.2552 2.95573 10.4063 3.80469 10.4063V12.4635C3.80469 12.8229 3.89323 13.1068 4.06771 13.3177C4.24479 13.5286 4.49479 13.6328 4.81771 13.6328C5.04687 13.6328 5.2526 13.5729 5.4401 13.4531C5.625 13.3307 5.85937 13.1432 6.14323 12.888L8.91406 10.4063H11.1536C12 10.4063 12.7135 10.2552 13.2891 9.95573C13.8646 9.65625 14.2995 9.21615 14.5911 8.64063C14.8854 8.06511 15.0312 7.35938 15.0312 6.52604V3.88021C15.0312 3.04427 14.8854 2.34115 14.5911 1.76302C14.2995 1.1875 13.8646 0.75 13.2891 0.450522C12.7135 0.151042 12 0 11.1536 0H3.88021ZM3.88021 1.5H11.1536C11.987 1.5 12.5938 1.69792 12.974 2.09635C13.3542 2.49219 13.5417 3.08594 13.5417 3.88021V6.52604C13.5417 7.31771 13.3542 7.91667 12.974 8.31511C12.5938 8.71615 11.987 8.91667 11.1536 8.91667H8.83333C8.58594 8.91667 8.38281 8.94011 8.22656 8.99219C8.07292 9.04427 7.91146 9.16146 7.7474 9.33854L5.20052 11.9349V9.60938C5.20052 9.35417 5.14844 9.17448 5.04167 9.07031C4.9349 8.96615 4.75521 8.91667 4.50781 8.91667H3.88021C3.04427 8.91667 2.4375 8.71615 2.0599 8.31511C1.67969 7.91667 1.48958 7.31771 1.48958 6.52604V3.88021C1.48958 3.08594 1.67969 2.49219 2.0599 2.09635C2.4375 1.69792 3.04427 1.5 3.88021 1.5ZM16.1016 3C16.1406 3.28125 16.1589 3.57292 16.1589 3.88021V4.5H16.2708C17.1068 4.5 17.7161 4.69792 18.0938 5.09635C18.4714 5.48958 18.6641 6.08594 18.6641 6.8802
11V9.59375C18.6641 10.388 18.4714 10.9844 18.0938 11.3802C17.7161 11.7839 17.1068 11.9818 16.2708 11.9818H15.6458C15.3984 11.9818 15.2188 12.0339 15.112 12.138C15.0052 12.2396 14.9505 12.4193 14.9505 12.6745V15.0052L12.4062 12.4089C12.2396 12.2292 12.0781 12.112 11.9245 12.0599C11.7708 12.0078 11.5677 11.9818 11.3203 11.9818C8.80729 11.9818 8.96094 11.9844 8.92188 11.9766L7.47656 13.2682C7.78646 13.362 8.27344 13.474 9 13.474H11.2344L14.0104 15.9557C14.2917 16.2109 14.5286 16.3984 14.7109 16.5182C14.8984 16.638 15.1042 16.6979 15.3333 16.6979C15.6562 16.6979 15.9089 16.5938 16.0833 16.3828C16.2604 16.1745 16.3464 15.888 16.3464 15.5339V13.474C17.1979 13.474 17.8307 13.3255 18.4089 13.0234C18.987 12.724 19.4167 12.2839 19.7135 11.7083C20.0052 11.1328 20.1536 10.4297 20.1536 9.59375V6.88021C20.1536 6.04427 20.0052 5.34115 19.7135 4.76302C19.4167 4.1875 18.987 3.75 18.4089 3.45052C17.8307 3.15104 17.1224 3 16.2708 3H16.1016ZM3.89583 3.7526C3.60156 3.7526 3.3776 3.99219 3.3776 4.27865C3.3776 4.57031 3.60156 4.80729 3.89583 4.80729H11.3281C11.6224 4.80729 11.8464 4.56771 11.8464 4.27865C11.8464 3.98958 11.6224 3.7526 11.3281 3.7526H3.89583ZM3.89583 6.01563C3.53385 6.01563 3.3776 6.33073 3.3776 6.55208C3.3776 6.84115 3.60156 7.07031 3.89583 7.07031H8.81771C9.10677 7.07031 9.33594 6.84635 9.33594 6.55208C9.33594 6.33594 9.17969 6.01563 8.81771 6.01563H3.89583Z" fill="currentColor" /> </svg> <span class="sidebar-side-toggle__counter" ng-if="$ctrl.pendingChangesCount > 0" >{{ $ctrl.pendingChangesCount }}</span > <span class="custom-tooltip">{{ \'Панель комментариев и правок\' | t }}</span> </button>'),s.put("/pages/edit/editContentItem/partials/suggestion-replies.html",'<p class="collab-write-uncertain" ng-if="$ctrl.suggestionDiscussionNotice(suggestion)" role="status" > <i class="fas fa-exclamation-triangle" aria-hidden="true"></i> {{ $ctrl.suggestionDiscussionNotice(suggestion) }} </p> <div class="comment-replies" ng-if="$ctrl.suggestionThreads[suggestion.suggestionId] && $ctrl.suggestionThreads[suggestion.suggestionId].length > 0" role="list" aria-label="{{ \'Ответы на обсуждение правки\' | t }}" > <div class="reply-item" ng-repeat="reply in $ctrl.suggestionThreads[suggestion.suggestionId] track by reply._id" role="listitem" data-reply-id="{{ ::reply._id }}" > <div class="reply-header"> <a class="reply-author" ng-href="/{{ ::reply.author.path }}" ng-if="::(reply.author && reply.author.path)" target="_blank" rel="noopener noreferrer" > <img class="author-avatar" ng-if="::reply.authorAvatar" ng-src="{{ ::reply.authorAvatar }}" alt="" ng-on-error="$event.target.style.display=\'none\'" /> <span class="author-avatar-placeholder" ng-if="::(!reply.authorAvatar)" title="{{ ::(reply.author && reply.author.name) || (\'Автор\' | t) }}" >{{ ::((reply.author && reply.author.name) || (\'А\' | t)).charAt(0) }}</span > <div class="sidebar-author-info"> <span class="author-name" >{{ ::(reply.author && reply.author.name) || (\'Автор\' | t) }}</span > <span class="sidebar-date">{{ ::reply.createdAt | date:\'dd.MM HH:mm\' }}</span> </div> </a> <span class="reply-author" ng-if="::(!reply.author || !reply.author.path)"> <img class="author-avatar" ng-if="::reply.authorAvatar" ng-src="{{ ::reply.authorAvatar }}" alt="" ng-on-error="$event.target.style.display=\'none\'" /> <span class="author-avatar-placeholder" ng-if="::(!reply.authorAvatar)" title="{{ ::(reply.author && reply.author.name) || (\'Автор\' | t) }}" >{{ ::((reply.author && reply.author.name) || (\'А\' | t)).charAt(0) }}</span > <div class="sidebar-author-info"> <span class="author-name" >{{ ::(reply.author && reply.author.name) || (\'Автор\' | t) }}</span > <span class="sidebar-date">{{ ::reply.createdAt | date:\'dd.MM HH:mm\' }}</span> </div> </span> <div class="item-actions-menu" ng-if="$ctrl.canEditReply(reply) || $ctrl.canDeleteReply(reply)" ng-class="{\'has-open-menu\': reply.menuOpen}" ng-click="$event.stopPropagation()" > <button type="button" class="ds-btn-link btn-icon item-menu-trigger reply-menu-trigger" ng-click="$ctrl.toggleItemMenu(reply, $event)" ng-disabled="$ctrl.isSuggestionReplyWriteBlocked(reply, suggestion)" aria-label="{{ \'Действия с ответом\' | t }}" > <i class="fas fa-ellipsis-v"></i> </button> <div class="item-dropdown" ng-if="reply.menuOpen" ng-click="$event.stopPropagation()"> <button type="button" class="item-dropdown-btn" ng-if="$ctrl.canEditReply(reply) && !reply.isEditing" ng-click="$ctrl.startEditReply(reply, suggestion); reply.menuOpen = false" ng-disabled="$ctrl.isSuggestionReplyWriteBlocked(reply, suggestion)" > {{ \'Редактировать\' | t }} </button> <button type="button" class="item-dropdown-btn item-dropdown-btn--danger" ng-if="$ctrl.canDeleteReply(reply)" ng-click="$ctrl.deleteSuggestionReply(suggestion, reply
1); reply.menuOpen = false" ng-disabled="$ctrl.isSuggestionReplyWriteBlocked(reply, suggestion)" > {{ \'Удалить\' | t }} </button> </div> </div> </div> <p class="collab-write-uncertain" ng-if="$ctrl.isSuggestionReplyWriteUncertain(reply)" role="status" > <i class="fas fa-exclamation-triangle" aria-hidden="true"></i> {{ \'Не удалось подтвердить изменение. Обновите страницу, прежде чем продолжить\' | t }} </p> <div class="reply-text" ng-if="!reply.isEditing" ng-bind-html="reply.text | inlineMarkdown | mentionLinksHtml" ></div> <div class="reply-edit-form" ng-if="reply.isEditing"> <textarea class="reply-edit-textarea" ng-model="reply.editText" mention-autocomplete rich-text-shortcuts aria-label="{{ \'Редактирование ответа на правку\' | t }}" rows="1" msd-elastic="msd-elastic" ng-keydown="$ctrl.onSuggestionReplyEditKeydown($event, suggestion, reply)" ng-readonly="$ctrl.isSuggestionReplyWriteBlocked(reply, suggestion)" ></textarea> <div class="reply-edit-actions"> <button type="button" class="btn-edit-cancel" ng-click="$ctrl.cancelEditReply(reply)" ng-disabled="$ctrl.isSuggestionReplyWritePending(reply)" > {{ \'Отменить\' | t }} </button> <button type="button" class="btn-edit-save" ng-click="$ctrl.saveEditSuggestionReply(suggestion, reply)" ng-disabled="!reply.editText || !reply.editText.trim() || $ctrl.isSuggestionReplyWriteBlocked(reply, suggestion)" > {{ \'Сохранить\' | t }} </button> </div> </div> </div> </div> <div class="comment-add-reply"> <div class="reply-input-wrapper"> <textarea ng-model="suggestion.newReplyText" placeholder="{{ \'Ответить...\' | t }}" aria-label="{{ \'Текст ответа на обсуждение правки\' | t }}" mention-autocomplete rich-text-shortcuts rows="1" msd-elastic="msd-elastic" ng-keydown="$ctrl.onSuggestionReplyKeydown($event, suggestion)" ng-readonly="$ctrl.isSuggestionDiscussionBlocked(suggestion)" ></textarea> <button type="button" class="reply-send-btn btn-icon" ng-click="$ctrl.addSuggestionReply(suggestion)" ng-disabled="!suggestion.newReplyText || !suggestion.newReplyText.trim() || $ctrl.isSuggestionDiscussionBlocked(suggestion)" aria-label="{{ \'Отправить ответ\' | t }}" > <i class="fas fa-paper-plane" aria-hidden="true"></i> <span class="custom-tooltip">{{ \'Отправить\' | t }}</span> </button> </div> </div>'),s.put("/pages/edit/editContentItem/partials/suggestion-type-label.html","<span class=\"suggestion-type-inline\" ng-if=\"suggestion.type === 'add'\">{{ 'Добавление' | t }}</span> <span class=\"suggestion-type-inline suggestion-type-inline--delete\" ng-if=\"suggestion.type === 'delete'\" >{{ 'Удаление' | t }}</span > <span class=\"suggestion-type-inline suggestion-type-inline--replace\" ng-if=\"suggestion.type === 'replace'\" >{{ 'Замена' | t }}</span > <span class=\"suggestion-type-inline suggestion-type-inline--format\" ng-if=\"suggestion.type === 'format'\" >{{ 'Формат' | t }}</span > <span class=\"suggestion-type-inline\" ng-if=\"suggestion.type === 'node-add'\" >{{ 'Вставка' | t }}</span > <span class=\"suggestion-type-inline suggestion-type-inline--delete\" ng-if=\"suggestion.type === 'node-delete'\" >{{ 'Удаление' | t }}</span > <span class=\"suggestion-type-inline suggestion-type-inline--update\" ng-if=\"suggestion.type === 'node-update'\" >{{ 'Правка' | t }}</span >"),s.put("/pages/edit/editContentItem/partials/tiptap-toolbar.html",'<div class="tiptap-editor-toolbar discours-toolbar" role="toolbar" aria-label="{{ \'Панель инструментов редактора\' | t }}" fixed-toolbar ng-mousedown="$event.preventDefault()" > <div class="tiptap-toolbar-buttons"> <div class="btn-group"> <button type="button" class="tiptap-btn" ng-class="{ active: $ctrl.isActive(\'bold\') }" ng-mousedown="$ctrl.execCommand(\'bold\', $event)" data-title="{{ \'жирный\' | t }}" aria-label="{{ \'Жирный текст\' | t }}" aria-pressed="{{$ctrl.isActive(\'bold\')}}" > <i class="df-editor-bold" aria-hidden="true"></i> </button> <button type="button" class="tiptap-btn" ng-class="{ active: $ctrl.isActive(\'italic\') }" ng-mousedown="$ctrl.execCommand(\'italic\', $event)" data-title="{{ \'курсив\' | t }}" aria-label="{{ \'Курсив\' | t }}" aria-pressed="{{$ctrl.isActive(\'italic\')}}" > <i class="df-editor-italic" aria-hidden="true"></i> </button> <div class="btn-dropdown" ng-class="{ open: $ctrl.listDropdownOpen }" role="group" aria-label="{{ \'Списки\' | t }}" > <button type="button" class="tiptap-btn btn-dropdown-toggle" ng-mousedown="$ctrl.toggleDropdown(\'listDropdownOpen\', $event)" data-title="{{ \'добавить список\' | t }}" aria-label="{{ \'Добавить список\' | t }}" aria-haspopup="true" aria-expanded="{{$ctrl.listDropdownOpen}}" > <i class="fas fa-list" aria-hidden="true"></i> </button> <div class="btn-dropdown-menu" ng-show="$ctrl.listDropdownOpen" role="menu"> <button type="button" class="tiptap-btn" ng-class="{ active: $ctrl.isActive(\'bulletList\') }" ng-mousedown="$ctrl.execCommand(\'bulletList\', $event); $ctrl.closeAllDropdowns()" data-title="{{ \'маркированный список\' | t }}" aria-label="{{ \'Маркированный список\' | t }}" role="menuitem" > <i class="fa fa-list-ul" aria-hidden="true"></i> </button> <button type="button" class="tiptap-btn" ng-class="{ active: $ctrl.isActive(\'orderedList\') }" ng-mousedown="$ctrl.execCommand(\'orderedList\', $event); $ctrl.closeAllDropdowns()" data-title="{{ \'нумерованный список\' | t }}" aria-label="{{ \'Нумерованный список\' | t }}" role="menuitem" > <i class="fa fa-list-ol" aria-hidden="true"></i> </button> </div> </div> <div class="btn-dropdown" ng-class="{ open: $ctrl.formatDropdownOpen }" role="group" aria-label="{{ \'Форматирование текста\' | t }}" > <button type="button" class="tiptap-btn btn-dropdown-toggle" ng-mousedown="$ctrl.toggleDropdown(\'formatDropdownOpen\', $event)" data-title="{{ \'форматирование текста\' | t }}" aria-label="{{ \'Форматирование текста\' | t }}" aria-haspopup="true" aria-expanded="{{$ctrl.formatDropdownOpen}}" > <i class="df-editor-text-tools" aria-hidden="true"></i> </button> <div class="btn-dropdown-menu" ng-show="$ctrl.formatDropdownOpen" role="menu"> <button type="button" class="tiptap-btn" ng-class="{ active: $ctrl.isActive(\'h2\') }" ng-mousedown="$ctrl.execCommand(\'h2\', $event); $ctrl.closeAllDropdowns()" data-title="{{ \'заголовок 1\' | t }}" aria-label="{{ \'Заголовок 1\' | t }}" role="menuitem" > <i class="df-editor-h1" aria-hidden="true"></i> </button> <button type="button" class="tiptap-btn" ng-class="{ active: $ctrl.isActive(\'h3\') }" ng-mousedown="$ctrl.execCommand(\'h3\', $event); $ctrl.closeAllDropdowns()" data-title="{{ \'заголовок 2\' | t }}" aria-label="{{ \'Заголовок 2\' | t }}" role="menuitem" > <i class="df-editor-h2" aria-hidden="true"></i> </button> <button type="button" class="tiptap-btn" ng-class="{ active: $ctrl.isActive(\'lead\') }" ng-mousedown="$ctrl.execCommand(\'lead\', $event); $ctrl.closeAllDropdowns()" data-title="{{ \'лид\' | t }}" aria-label="{{ \'Лид\' | t }}" role="menuitem" > <i class="fa fa-paragraph" aria-hidden="true"></i> </button> <button type="button" class="tiptap-btn" ng-class="{ active: $ctrl.isActive(\'highlight\') }" ng-mousedown="$ctrl.execCommand(\'highlight\', $event); $ctrl.closeAllDropdowns()" data-title="{{ \'хайлайт\' | t }}" aria-label="{{ \'Хайлайт\' | t }}" role="menuitem" > <svg class="highlighter-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 544 512" width="16" height="16" fill="currentColor" aria-hidden="true" > <path d="M0 479.98L99.92 512l35.45-35.45-67.04-67.04L0 479.98zm124.61-240.01a36.592 36.592 0 0 0-10.79 38.1l13.05 42.83-50.93 50.94 96.23 96.23 50.86-50.86 42.74 13.08a36.46 36.46 0 0 0 38.09-10.79l94.12-114.14-159.2-159.2-114.17 93.81zm287.07-18.96L527.95 105.5c21.4-21.4 21.4-56.13 0-77.53l-43.91-43.91c-21.4-21.4-56.13-21.4-77.53 0L290.95 99.52l120.73 121.49z" /> </svg> </button> <button type="button" class="tiptap-btn" ng-class="{ active: $ctrl.isActive(\'blockquote\') }" ng-mousedown="$ctrl.execCommand(\'blockquote\', $event); $ctrl.closeAllDropdowns()" data-title="{{ \'ударная цитата\' | t }}" aria-label="{{ \'Ударная цитата\' | t }}" role="menuitem" > <i class="df-editor-quote" aria-hidden="true"></i> </button> </div> </div> <div class="btn-dropdown" ng-class="{ open: $ctrl.wrapperDropdownOpen }" role="group" aria-label="{{ \'Подвёрстки\' | t }}" > <button type="button" class="tiptap-btn btn-dropdown-toggle" ng-mousedown="$ctrl.toggleDropdown(\'wrapperDropdownOpen\', $event)" data-title="{{ \'подвёрстки\' | t }}" aria-label="{{ \'Подвёрстки\' | t }}" aria-haspopup="true" aria-expanded="{{$ctrl.wrapperDropdownOpen}}" > <span class="btn-icon-frame" aria-hidden="true"></span> </button> <div class="btn-dropdown-menu" ng-show="$ctrl.wrapperDropdownOpen" role="menu"> <button type="button" class="tiptap-btn" ng-class="{ active: $ctrl.isActive(\'taSelectionFrame\') }" ng-mousedown="$ctrl.execCommand(\'taSelectionFrame\', $event); $ctrl.closeAllDropdowns()" data-title="{{ \'рамка\' | t }}" aria-label="{{ \'Рамка\' | t }}" role="menuitem" > <span class="btn-icon-frame" aria-hidden="true"></span> </button> <button type="button" class="tiptap-btn" ng-class="{ active: $ctrl.isActive(\'taSub\') }" ng-mousedown="$ctrl.execCommand(\'taSub\', $event); $ctrl.closeAllDropdowns()" data-title="{{ \'подвёрстка\' | t }}" aria-label="{{ \'Подвёрстка\' | t }}" role="menuitem" > <span class="btn-icon-sub" aria-hidden="true"></span> </button> <button type="button" class="tiptap-btn" ng-class="{ active: $ctrl.isActive(\'taBorderSub\') }" ng-mousedown="$ctrl.execCommand(\'taBorderSub\', $event); $ctrl.closeAllDropdowns()" data-title="{{ \'подвёрстка с рамкой\' | t }}" aria-label="{{ \'Подвёрстка с рамкой\' | t }}" role="menuitem" > <span class="btn-icon-border-sub" aria-hidden="true"></span> </button> <button type="button" class="tiptap-btn" ng-class="{ active: $ctrl.isActive(\'taQuotation\') }" ng-mousedown="$ctrl.execCommand(\'taQuotation\', $event); $ctrl.closeAllDropdowns()" data-title="{{ \'цитата из источника\' | t }}" aria-label="{{ \'Цитата из источника\' | t }}" role="menuitem" > <span class="btn-icon-quotation" aria-hidden="true"></span> </button> <button type="button" class="tiptap-btn" ng-class="{ active: $ctrl.isActive(\'taEpigraph\') }" ng-mousedown="$ctrl.execCommand(\'taEpigraph\', $event); $ctrl.closeAllDropdowns()" data-title="{{ \'эпиграф\' | t }}" aria-label="{{ \'Эпиграф\' | t }}" role="menuitem" > <span class="btn-icon-epigraph" aria-hidden="true"></span> </button> </div> </div> <div class="btn-dropdown" ng-class="{ open: $ctrl.specialDropdownOpen }" role="group" aria-label="{{ \'Специальные блоки\' | t }}" > <button type="button" class="tiptap-btn btn-dropdown-toggle" ng-mousedown="$ctrl.toggleDropdown(\'specialDropdownOpen\', $event)" data-title="{{ \'добавить спецблок\' | t }}" aria-label="{{ \'Добавить специальный блок\' | t }}" aria-haspopup="true" aria-expanded="{{$ctrl.specialDropdownOpen}}" > <i class="fa fa-plus" aria-hidden="true"></i> </button> <div class="btn-dropdown-menu" ng-show="$ctrl.specialDropdownOpen" role="menu"> <button type="button" class="tiptap-btn" ng-mousedown="$ctrl.execCommand(\'insertToc\', $event); $ctrl.closeAllDropdowns()" data-title="{{ \'вставить оглавление\' | t }}" aria-label="{{ \'Вставить оглавление\' | t }}" role="menuitem" > <i class="fa fa-list-alt" aria-hidden="true"></i> </button> <button type="button" class="tiptap-btn" ng-mousedown="$ctrl.execCommand(\'insertReadAlso\', $event); $ctrl.closeAllDropdowns()" data-title="{{ \'читайте также\' | t }}" aria-label="{{ \'Читайте также\' | t }}" role="menuitem" > <i class="fa fa-newspaper" aria-hidden="true"></i> </button> <button type="button" class="tiptap-btn" ng-mousedown="$ctrl.execCommand(\'pdf\', $event); $ctrl.closeAllDropdowns()" data-title="{{ \'вставить PDF\' | t }}" aria-label="{{ \'Вставить PDF документ\' | t }}" role="menuitem" > <svg class="pdf-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512" width="14" height="16" fill="currentColor" aria-hidden="true" > <path d="M64 0C28.7 0 0 28.7 0 64v384c0 35.3 28.7 64 64 64h256c35.3 0 64-28.7 64-64V160H256c-17.7 0-32-14.3-32-32V0H64zm192 0v128h128L256 0zM112 256c0-8.8 7.2-16 16-16h24c26.5 0 48 21.5 48 48s-21.5 48-48 48h-8v32c0 8.8-7.2 16-16 16s-16-7.2
1-16-16V256zm40 48h8c8.8 0 16-7.2 16-16s-7.2-16-16-16h-8v32zm64-48c0-8.8 7.2-16 16-16h16c26.5 0 48 21.5 48 48v64c0 26.5-21.5 48-48 48h-16c-8.8 0-16-7.2-16-16V256zm32 112h0c8.8 0 16-7.2 16-16v-64c0-8.8-7.2-16-16-16v96zm80-96h32c8.8 0 16-7.2 16-16s-7.2-16-16-16h-48c-8.8 0-16 7.2-16 16v128c0 8.8 7.2 16 16 16s16-7.2 16-16v-48h16c8.8 0 16-7.2 16-16s-7.2-16-16-16h-16v-32z" /> </svg> </button> <button type="button" class="tiptap-btn" ng-mousedown="$ctrl.execCommand(\'insertSpoiler\', $event); $ctrl.closeAllDropdowns()" data-title="{{ \'спойлер\' | t }}" aria-label="{{ \'Спойлер (подкат)\' | t }}" role="menuitem" > <span class="btn-icon-spoiler" aria-hidden="true"></span> </button> </div> </div> </div> <div class="btn-group"> <button type="button" class="tiptap-btn" ng-class="{ active: $ctrl.isActive(\'link\') }" ng-mousedown="$ctrl.execCommand(\'link\', $event)" data-title="{{ \'вставить ссылку\' | t }}" aria-label="{{ \'Вставить ссылку\' | t }}" > <i class="df-editor-link"></i> </button> <button type="button" class="tiptap-btn" ng-class="{ active: $ctrl.isActive(\'footnote\') }" ng-mousedown="$ctrl.execCommand(\'footnote\', $event)" data-title="{{ \'вставить сноску\' | t }}" aria-label="{{ \'Вставить сноску\' | t }}" > <i class="df-editor-footnote"></i> </button> </div> <div class="btn-group"> <button type="button" class="tiptap-btn" ng-mousedown="$ctrl.execCommand(\'image\', $event)" data-title="{{ \'добавить изображение\' | t }}" aria-label="{{ \'Добавить изображение\' | t }}" > <i class="df-editor-image"></i> </button> <button type="button" class="tiptap-btn" ng-mousedown="$ctrl.execCommand(\'video\', $event)" data-title="{{ \'добавить видео\' | t }}" aria-label="{{ \'Добавить видео\' | t }}" > <i class="fab fa-youtube"></i> </button> <button type="button" class="tiptap-btn" ng-mousedown="$ctrl.execCommand(\'embed\', $event)" data-title="{{ \'вставить медиа\' | t }}" aria-label="{{ \'Вставить медиа\' | t }}" > <i class="fa fa-code"></i> </button> <button type="button" class="tiptap-btn" ng-if="$ctrl.isAdmin" ng-class="{ active: $ctrl.htmlMode }" ng-mousedown="$ctrl.execCommand(\'toggleHtml\', $event)" data-title="{{ \'посмотреть html\' | t }}" aria-label="{{ \'Посмотреть HTML\' | t }}" > <i class="fas fa-wrench"></i> </button> </div> </div> </div>'),s.put("/pages/edit/editContentItem/types/Article.html",'<div class="title-input-row tw:flex tw:flex-wrap" ng-hide="$ctrl.isPreviewMode"> <div class="title-col tw:sm:w-8/12 tw:lg:w-9/12"> <div class="form-group tw:font-bold title" ng-class="{\'has-error\': formErrors.title}"> <textarea class="ds-input big-textarea placeholder-big" placeholder="{{ \'Заголовок\' | t }}" ng-model="contentItem.title" ng-change="formErrors.title = false" maxlength="120" textarea-limit-rows="textarea-limit-rows" rows="1" msd-elastic aria-label="{{ \'Заголовок статьи\' | t }}" aria-invalid="{{ !!formErrors.title }}" aria-describedby="form-error-title" ></textarea> <span id="form-error-title" class="help-block" ng-show="formErrors.title" role="alert" >{{ \'Заголовок обязателен\' | t }}</span > </div> </div> <div class="cover-col tw:sm:w-4/12 tw:lg:w-3/12"> <div class="form-group"> <button class="big-button" type="button" ngf-select="$ctrl.uploadImage($file)" ngf-drop="$ctrl.uploadImage($file)" ngf-drag-over-class="\'drag-over\'" ngf-accept="\'image/*\'" ng-disabled="$ctrl.imageUploading[\'root\']" ng-hide="contentItem.thumborId" > <p class="fa fa-paperclip fa-4x"></p> <div>{{ \'Загрузить обложку\' | t }}<br />(jpeg, png, gif)</div> <span class="fas fa-sync-alt fa-spin floating-text left" ng-show="$ctrl.imageUploading[\'root\']" ></span> </button> <div class="post-image-wrapper" ng-if="contentItem.thumborId"> <div class="cover-actions cover-actions--desktop"> <button type="button" class="cover-action-btn" ng-click="$ctrl.cropCoverImage()" ng-disabled="$ctrl.cropUploading" aria-label="{{ \'Кадрировать обложку\' | t }}" data-tooltip="{{ \'Кадрировать\' | t }}" > <i class="fas fa-crop-alt" aria-hidden="true"></i> </button> <button type="button" class="cover-action-btn" ngf-select="$ctrl.uploadImage($file)" ngf-accept="\'image/*\'" ng-disabled="$ctrl.cropUploading || $ctrl.imageUploading[\'root\']" aria-label="{{ \'Заменить обложку\' | t }}" data-tooltip="{{ \'Заменить\' | t }}" > <i class="fas fa-image" aria-hidden="true"></i> </button> <button type="button" class="cover-action-btn cover-action-btn--delete" ng-click="$ctrl.deleteCoverImage()" ng-disabled="$ctrl.cropUploading" aria-label="{{ \'Удалить обложку\' | t }}" data-tooltip="{{ \'Удалить\' | t }}" > <i class="fas fa-times" aria-hidden="true"></i> </button> </div> <ng-include src="\'/pages/edit/editContentItem/partials/cover-actions-mobile.html\'" ></ng-include> <span class="fas fa-sync-alt fa-spin crop-upload-spinner" ng-show="$ctrl.cropUploading" ></span> <img class="tw:block tw:max-w-full tw:h-auto thumbmail" ng-class="{\'is-uploading\': $ctrl.cropUploading }" ng-src="{{ contentItem.thumborId | imageUrl: 300 }}" ng-click="$ctrl.cropCoverImage()" alt="{{ contentItem.imageCaption || (\'Обложка:\' | t) + \' \' + (contentItem.title || (\'материал\' | t)) + \' \' + (\'— Дискурс\' | t) }}" /> </div> <div class="image-caption-input"> <input type="text" name="imageCaption" placeholder="{{ \'Подпись изображения\' | t }}" class="material-style" ng-model="contentItem.imageCaption" /> <button type="button" class="caption-edit-icon" data-tooltip="{{ \'Редактировать подпись\' | t }}" ng-click="$ctrl.openImageCaptionEditor($event)" > <i class="fas fa-pencil-alt" aria-hidden="true"></i> </button> </div> </div> </div> </div> <editor-header ng-if="$ctrl.showEditorHeader" doc-title="contentItem.title" slug="contentItem.slug" content-status="contentItem.status" content-link="contentItem.link" collab-status="$ctrl.collabStatus" connected-users="$ctrl.connectedUsers" is-offline-mode="$ctrl.isOfflineMode" collab-permission-denied="$ctrl.collabPermissionDenied" is-reconnecting="$ctrl.isReconnecting" tiptap-editor="$ctrl.tiptapEditor" is-preview-mode="$ctrl.isPreviewMode" suggestion-mode="$ctrl.suggestionMode" sidebar-visible="$ctrl.sidebarVisible" pending-count="$ctrl.pendingChangesCount" collab-sync-pending="$ctrl.collabSyncPending" is-saving="$ctrl.isSaving" is-publishing="$ctrl.isPublishing" is-saving-version="$ctrl.savingVersion" is-save-success="$ctrl.saveSuccess" is-save-error="$ctrl.isSaveError" last-version-saved-at="$ctrl.lastVersionSavedAt" has-unsaved-changes="$ctrl.hasUnsavedChanges" has-draft="$ctrl.hasDraft" can-edit="$ctrl.canEdit === true" is-author="!$ctrl.contentItemCreatorId || $ctrl.userId === $ctrl.contentItemCreatorId" show-collab-control
1s="!!contentItem.slug" show-invite-button="$ctrl.canEdit === true && contentItem.slug" invite-count="$ctrl.inviteCount" on-open-invite="$ctrl.openInviteModal()" on-mode-change="$ctrl.onEditorModeChange(mode)" on-toggle-sidebar="$ctrl.toggleSidebar()" on-save="contentItem.slug ? $ctrl.saveAndCreateVersion() : $ctrl.saveDraft()" on-save-version-dialog="$ctrl.openVersionNameDialog()" on-publish="$ctrl.sendFormForPublish()" on-validate-before-publish="$ctrl.validateBeforePublish()" on-retry-connection="$ctrl.retryCollabConnection()" on-versions-click="$ctrl.toggleVersions()" night-mode="$ctrl.darkMode" on-toggle-night-mode="$ctrl.toggleDarkMode()" on-open-settings="$ctrl.openPublishSettings()" on-run-typographer="$ctrl.applyTypography()" word-count="$ctrl.wordCount" char-count="$ctrl.charCount" initial-para-count="$ctrl.paraCount" is-admin="$ctrl.isAdmin" on-clean-code="$ctrl.cleanHtmlContent()" initial-last-modified="$ctrl.initialLastModified" margin-view-mode="$ctrl.marginViewMode" on-toggle-margin-view="$ctrl.toggleMarginView()" quiet-mode="$ctrl.quietMode" on-toggle-quiet-mode="$ctrl.toggleQuietMode()" is-logged-in="$ctrl.isLoggedIn" user-avatar="$ctrl.userAvatarId" user-path="$ctrl.userPathSlug" ></editor-header> <invite-collaborators opened="$ctrl.inviteModalOpen" document-name="$ctrl.collabDocumentName" on-invites-changed="$ctrl.onInvitesChanged(invites)" ></invite-collaborators> <div class="post-preview tiptap-preview-mode" ng-if="$ctrl.isPreviewMode && $ctrl.previewPost"> <div class="button-container"> <button type="button" class="preview-close-button" ng-click="$ctrl.togglePreviewMode()"> <svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg" > <path d="M17.9975 4.89363L13.1038 0L0 13.1064V18H4.89363L17.9975 4.89363ZM13.105 2.55674L15.4421 4.89383L14.4612 5.87591L12.1241 3.53882L13.105 2.55674ZM10.8453 4.81648L13.1824 7.15357L5.42174 14.9137L3.08485 12.5756L10.8453 4.81648ZM1.80725 16.1915V13.8544L4.14434 16.1915H1.80725Z" fill="currentColor" /> </svg> {{ \'Продолжить редактировать\' | t }} </button> </div> <div class="open-post progress"> <div class="author-category" ng-class="{\'no-author\': !$ctrl.previewPost.author }"> <span class="category" ng-if="$ctrl.previewPost.category"> <a ng-href="{{ $ctrl.previewPost.categoryLink }}" rel="noopener noreferrer" target="_blank" >{{ $ctrl.previewPost.category }}</a > </span> <span class="author" ng-if="$ctrl.previewPost.author"> <a ng-href="{{ $ctrl.previewPost.authorLink }}" rel="noopener noreferrer" target="_blank" ng-if="$ctrl.previewPost.authorLink" >{{ $ctrl.previewPost.author }}</a > <span ng-if="!$ctrl.previewPost.authorLink">{{ $ctrl.previewPost.author }}</span> </span> </div> <h1 class="title">{{ $ctrl.previewPost.title }}</h1> <div class="open-post-title-image-wrapper" ng-if="$ctrl.previewPost.thumborId" ng-class="{\'is-image-loaded\': previewTitleImageLoaded}" > <img class="open-post-title-image fancybox-img" fancybox-img ng-if="$ctrl.previewPost.imageCaption" fetchpriority="high" loading="eager" decoding="async" sizes="(min-width: 768px) 66vw, 100vw" ng-src="{{ $ctrl.previewPost.thumborId | imageUrl: 750 }}" ng-srcset="{{ $ctrl.previewPost.thumborId | imageUrl: 320 }} 320w, {{ $ctrl.previewPost.thumborId | imageUrl: 480 }} 480w, {{ $ctrl.previewPost.thumborId | imageUrl: 640 }} 640w, {{ $ctrl.previewPost.thumborId | imageUrl: 960 }} 960w, {{ $ctrl.previewPost.thumborId | imageUrl: 1280 }} 1280w, {{ $ctrl.previewPost.thumborId | imageUrl: 1920 }} 1920w, {{ $ctrl.previewPost.thumborId | imageUrl: 2560 }} 2560w" alt="{{ ($ctrl.previewPost.imageCaption | stripHTML) + \'. \' + ($ctrl.previewPost.title | stripHTML) + \' \' + (\'— Дискурс\' | t) }}" title="{{ ($ctrl.previewPost.imageCaption | stripHTML) + \'. \' + ($ctrl.previewPost.title | stripHTML) + \' \' + (\'— Дискурс\' | t) }}" ng-image-onload="previewTitleImageLoaded = true" /> <img class="open-post-title-image fancybox-img" fancybox-img ng-if="!$ctrl.previewPost.imageCaption" fetchpriority="high" loading="eager" decoding="async" sizes="(min-width: 768px) 66vw, 100vw" ng-src="{{ $ctrl.previewPost.thumborId | imageUrl: 750 }}" ng-srcset="{{ $ctrl.previewPost.thumborId | imageUrl: 320 }} 320w, {{ $ctrl.previewPost.thumborId | imageUrl: 480 }} 480w, {{ $ctrl.previewPost.thumborId | imageUrl: 640 }} 640w, {{ $ctrl.previewPost.thumborId | imageUrl: 960 }} 960w, {{ $ctrl.previewPost.thumborId | imageUrl: 1280 }} 1280w, {{ $ctrl.previewPost.thumborId | imageUrl: 1920 }} 1920w, {{ $ctrl.previewPost.thumborId | imageUrl: 2560 }} 2560w" alt="{{ \'Обложка:\' | t }} {{ $ctrl.previewPost.title }} {{ \'— Дискурс\' | t }}" title="{{ $ctrl.previewPost.title }} {{ \'— Дискурс\' | t }}" ng-image-onload="previewTitleImageLoaded = true" /> </div> <p class="image-description" ng-if="$ctrl.previewPost.imageCaption" ng-bind-html="$ctrl.previewPost.imageCaption | trustedHtml" ></p> <div class="post-body" ng-bind-html="$ctrl.previewPost.body | trustedHtml" post-links="post-links" post-images="post-images" social-text="social-text" footnote-text="footnote-text" pdf-viewer-hydrate disclaimer="{{ $ctrl.previewPost.publishedAt }}" ></div> <div class="footer-post"> <hr class="tw:mt-2.5 tw:mb-5" /> <div class="post-meta-footer"> <div class="post-meta-footer__author"> <div class="author-title" ng-if="$ctrl.previewPost.author"> <a ng-href="{{ $ctrl.previewPost.authorLink }}" rel="noopener noreferrer" target="_blank" ng-if="$ctrl.previewPost.authorLink" >{{ $ctrl.previewPost.author }}</a >
1 <span ng-if="!$ctrl.previewPost.authorLink">{{ $ctrl.previewPost.author }}</span> </div> <div class="date-time"> <span class="time" ng-if="$ctrl.previewPost.publishedAt" >{{ $ctrl.previewPost.publishedAt | amDateFormat: \'D MMMM YYYY\' }}</span ><span class="time" ng-if="!$ctrl.previewPost.publishedAt && $ctrl.previewPost.postedAt" >{{ $ctrl.previewPost.postedAt | amDateFormat: \'D MMMM YYYY\' }}</span > </div> </div> </div> <ul class="tags tw:mt-2.5" ng-if="$ctrl.previewPost.tags.length"> <li ng-repeat="tag in $ctrl.previewPost.tags track by tag.slug"> <a ng-href="/tags/{{ tag.slug }}" rel="noopener noreferrer" target="_blank" >#{{ tag.title }}</a > </li> </ul> </div> </div> <div class="button-container bottom-button" ng-if="$ctrl.showBottomEditButton"> <button type="button" class="preview-close-button bottom" ng-click="$ctrl.togglePreviewMode()"> <svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg" > <path d="M17.9975 4.89363L13.1038 0L0 13.1064V18H4.89363L17.9975 4.89363ZM13.105 2.55674L15.4421 4.89383L14.4612 5.87591L12.1241 3.53882L13.105 2.55674ZM10.8453 4.81648L13.1824 7.15357L5.42174 14.9137L3.08485 12.5756L10.8453 4.81648ZM1.80725 16.1915V13.8544L4.14434 16.1915H1.80725Z" fill="currentColor" /> </svg> {{ \'Продолжить редактировать\' | t }} </button> </div> </div> <div class="form-group discours-editor big tiptap-mode" ng-show="!$ctrl.isPreviewMode" ng-class="{\'has-editor-header\': $ctrl.showEditorHeader, \'quiet-mode\': $ctrl.quietMode }" > <div class="tiptap-loading" ng-if="!$ctrl.tiptapReady || $ctrl.collabSyncPending"> <div class="loading-progress-bar"> <div class="loading-progress-fill"></div> </div> <div class="tiptap-loading-skeleton"> <div class="tiptap-loading-skeleton__line"></div> <div class="tiptap-loading-skeleton__line tiptap-loading-skeleton__line--90"></div> <div class="tiptap-loading-skeleton__line tiptap-loading-skeleton__line--75"></div> <div class="tiptap-loading-skeleton__gap"></div> <div class="tiptap-loading-skeleton__line"></div> <div class="tiptap-loading-skeleton__line tiptap-loading-skeleton__line--85"></div> <div class="tiptap-loading-skeleton__line tiptap-loading-skeleton__line--60"></div> <div class="tiptap-loading-skeleton__gap"></div> <div class="tiptap-loading-skeleton__line tiptap-loading-skeleton__line--90"></div> <div class="tiptap-loading-skeleton__line tiptap-loading-skeleton__line--75"></div> </div> </div> <div ng-if="$ctrl.commenterReadOnly && !$ctrl.loginForReviewAvailable && $ctrl.tiptapReady" class="backup-recovery-banner anonymous-suggestions-banner" > <div class="backup-message"> <i class="fas fa-comment"></i> <span>{{ \'Режим комментирования — выделите текст, чтобы оставить комментарий.\' | t }}</span> </div> </div> <div ng-if="$ctrl.loginForReviewAvailable && $ctrl.tiptapReady" class="backup-recovery-banner anonymous-suggestions-banner" > <div class="backup-message"> <i class="fas fa-key"></i> <span>{{ \'Войдите, чтобы оставлять правки в режиме рецензирования.\' | t }}</span> </div> <div class="backup-actions"> <button type="button" class="btn-restore" ng-click="$ctrl.openLoginModal()"> {{ \'Войти\' | t }} </button> </div> </div> <div ng-if="$ctrl.hasLocalBackup && contentItem.slug && !$ctrl.collabSyncPending" class="backup-recovery-banner" ng-class="{\'is-collapsed\': $ctrl.backupBannerCollapsed}" > <button type="button" ng-if="$ctrl.backupBannerCollapsed" class="backup-collapsed-indicator" ng-click="$ctrl.expandBackupBanner()" aria-label="{{ \'Показать резервную копию\' | t }}" title="{{ \'Показать резервную копию\' | t }}" > <i class="fas fa-history"></i> <span>{{ \'Есть резервная копия\' | t }}</span> </button> <div ng-if="!$ctrl.backupBannerCollapsed" class="backup-message"> <i class="fas fa-history"></i> <span >{{ \'Резервная копия от\' | t }} {{ $ctrl.localBackupTime | date:\'dd.MM.yyyy HH:mm\' }}. <span ng-bind="\'Восстановление заменит весь текст, в том числе правки соавторов. Текущий текст сначала сохранится версией.\' | t" ></span ></span> </div>
1 <div ng-if="!$ctrl.backupBannerCollapsed" class="backup-actions"> <button type="button" class="btn-restore" ng-click="$ctrl.restoreFromBackup()" ng-disabled="$ctrl.restoringBackup" > <i class="fas fa-undo"></i> {{ \'Восстановить\' | t }} </button> <button type="button" class="btn-discard" ng-click="$ctrl.discardBackup()"> <i class="fas fa-times" aria-hidden="true"></i> {{ \'Сохранить как версию\' | t }} </button> </div> </div> <div ng-if="$ctrl.tiptapReady" class="tiptap-editor-container" ng-class="{\'is-syncing\': $ctrl.collabSyncPending}" > <div class="toc-nav-container" id="toc-nav-container-article"></div> <ng-include src="\'/pages/edit/editContentItem/partials/sidebar-side-toggle.html\'"></ng-include> <div class="tiptap-editor-wrapper"> <ng-include ng-if="!$ctrl.commenterReadOnly" src="\'/pages/edit/editContentItem/partials/tiptap-toolbar.html\'" ></ng-include> <div class="editor-with-margin-host" ng-class="{\'margin-view-active\': $ctrl.marginViewMode, \'sheet-open\': $ctrl.sidebarVisible }" > <tiptap-editor ng-model="contentItem.body" placeholder="{{ \'Место для вашей истории…\' | t }}" user-id="$ctrl.userId" user-name="$ctrl.userName" user-slug="$ctrl.userSlug" user-avatar="$ctrl.userAvatar" editor-instance="$ctrl.tiptapEditor" content-item="contentItem" collab-server-url="{{ $ctrl.collabServerUrl }}" collab-document-name="$ctrl.collabDocumentName" collab-token="$ctrl.collabToken" collab-status="$ctrl.collabStatus" collab-permission-denied="$ctrl.collabPermissionDenied" is-offline-mode="$ctrl.isOfflineMode" is-reconnecting="$ctrl.isReconnecting" on-collab-synced="$ctrl.onCollabSynced()" on-collab-fallback="$ctrl.onCollabFallback(reason)" on-initial-content-ready="$ctrl.onInitialContentReady()" initial-content="$ctrl.initialContent" ></tiptap-editor> <ng-include src="\'/pages/edit/editContentItem/partials/margin-view.html\'"></ng-include> </div> <div class="tiptap-sidebar" ng-class="{ visible: $ctrl.sidebarVisible }"> <button type="button" class="sheet-drag-handle" aria-label="{{ \'Изменить высоту панели\' | t }}" > <span class="sheet-drag-grip" aria-hidden="true"></span> </button> <div class="sidebar-header"> <div class="sidebar-tabs"> <button type="button" class="sidebar-tab" ng-class="{ active: $ctrl.sidebarTab === \'comments\' }" ng-click="$ctrl.sidebarTab = \'comments\'" data-collab-comments-tab > {{ \'Комментарии\' | t }} <span class="tab-count" ng-if="$ctrl.openCommentsCount" title="{{ \'Нерешённые комментарии\' | t }}" >({{ $ctrl.openCommentsCount }})</span > </button> <button type="button" class="sidebar-tab" ng-class="{ active: $ctrl.sidebarTab === \'suggestions\' }" ng-click="$ctrl.sidebarTab = \'suggestions\'" > {{ \'Правки\' | t }} <span class="tab-count" ng-if="$ctrl.suggestions.length" >({{ $ctrl.suggestions.length }})</span > </button> </div> <div class="sidebar-header-actions"> <div ng-if="!$ctrl.commentsLoading && $ctrl.sidebarTab === \'comments\'" ng-include="\'/pages/edit/editContentItem/partials/comment-filter.html\'" ></div> <button type="button" class="sidebar-close" ng-click="$ctrl.toggleSidebar()" title="{{ \'Закрыть\' | t }}" > <i class="fas fa-times" aria-hidden="true"></i> </button> </div> </div> <ng-include class="sidebar-capability-notice" ng-if="$ctrl.collabCommentStoreUnavailable" src="\'/pages/edit/editContentItem/partials/collab-capability-notice.html\'" ></ng-include> <div class="sidebar-content" ng-if="$ctrl.sidebarTab === \'comments\'"> <div ng-if="$ctrl.commentsLoading" class="comments-skeleton" aria-busy="true" aria-label="{{ \'Загрузка комментариев\' | t }}" > <div class="comments-skeleton__item" ng-repeat="i in [1,2,3] track by $index"> <div class="comments-skeleton__author"></div> <div class="comments-skeleton__text"></div> <div class="comments-skeleton__meta"></div> </div> </div> <div class="sidebar-empty" ng-if="!$ctrl.commentsLoading && !$ctrl.comments.length && !$ctrl.collabCommentStoreUnavailable" > <i class="fas fa-comment"></i> <p>{{ \'Нет комментариев\' | t }}</p> <small>{{ \'Выделите текст и нажмите кнопку комментария\' | t }}</small> </div>
1 <ng-include ng-if="!$ctrl.commentsLoading" src="\'/pages/edit/editContentItem/partials/comment-list.html\'" ></ng-include> </div> <div class="sidebar-content" ng-if="$ctrl.sidebarTab === \'suggestions\'"> <div class="sidebar-empty" ng-if="!$ctrl.suggestions.length"> <i class="fas fa-check-circle"></i> <p>{{ \'Нет ожидающих правок\' | t }}</p> <small>{{ \'Включите режим правок для отслеживания изменений\' | t }}</small> </div> <div class="suggestion-list" ng-if="$ctrl.suggestions.length"> <div class="suggestion-actions-all" ng-if="$ctrl.suggestions.length > 1 && $ctrl.canModerate()" > <button type="button" class="ds-btn ds-btn-sm ds-btn-success" ng-click="$ctrl.acceptAllSuggestions()" ng-disabled="$ctrl.isSuggestionBulkActionBlocked()" > <i class="fas fa-check-double"></i> {{ \'Принять все\' | t }} </button> <button type="button" class="ds-btn ds-btn-sm ds-btn-danger" ng-click="$ctrl.rejectAllSuggestions()" ng-disabled="$ctrl.isSuggestionBulkActionBlocked()" > <i class="fas fa-times" aria-hidden="true"></i> {{ \'Отклонить все\' | t }} </button> </div> <div class="suggestion-item" ng-repeat="suggestion in $ctrl.suggestions | limitTo:$ctrl.suggestionsDisplayLimit track by suggestion.suggestionId" ng-class="[suggestion.type, { expanded: suggestion.isExpanded }]" data-sidebar-suggestion-id="{{ suggestion.suggestionId }}" ng-click="$ctrl.toggleSuggestionExpand(suggestion)" role="group" ng-attr-aria-label="{{ ::(\'Правка от\' | t) + \' \' + (suggestion.authorName || (\'Автор\' | t)) }}" > <div class="suggestion-header"> <div class="suggestion-header-main"> <a class="suggestion-author" ng-href="/{{ suggestion.authorSlug }}" ng-if="suggestion.authorSlug" rel="noopener noreferrer" target="_blank" ng-click="$event.stopPropagation()" > <img class="author-avatar" ng-if="suggestion.authorAvatar" ng-src="{{ suggestion.authorAvatar }}" alt="" ng-on-error="$event.target.style.display=\'none\'" /> <span class="author-avatar-placeholder" ng-if="!suggestion.authorAvatar && suggestion.authorId" title="{{ suggestion.authorName || (\'Автор\' | t) }}" >{{ (suggestion.authorName || (\'А\' | t)).charAt(0) }}</span > <div class="sidebar-author-info"> <span class="author-name">{{ suggestion.authorName || (\'Автор\' | t) }}</span> <span class="sidebar-date" >{{ suggestion.createdAt | date:\'dd.MM HH:mm\' }}</span > </div> </a> <span class="suggestion-author" ng-if="!suggestion.authorSlug"> <img class="author-avatar" ng-if="suggestion.authorAvatar" ng-src="{{ suggestion.authorAvatar }}" alt="" ng-on-error="$event.target.style.display=\'none\'" /> <span class="author-avatar-placeholder" ng-if="!suggestion.authorAvatar && suggestion.authorId" title="{{ suggestion.authorName || (\'Автор\' | t) }}" >{{ (suggestion.authorName || (\'А\' | t)).charAt(0) }}</span > <div class="sidebar-author-info"> <span class="author-name" >{{ !suggestion.authorId ? (\'Анонимно\' | t) : (suggestion.authorName || (\'Автор\' | t)) }}</span > <span class="sidebar-date" >{{ suggestion.createdAt | date:\'dd.MM HH:mm\' }}</span > </div> </span> </div> <div class="suggestion-actions" ng-click="$event.stopPropagation()"> <button type="button" class="ds-btn-link btn-icon card-disclosure" ng-click="$ctrl.toggleSuggestionDisclosure(suggestion)" ng-attr-aria-expanded="{{ suggestion.isExpanded ? \'true\' : \'false\' }}" ng-attr-aria-controls="{{ suggestion.isExpanded ? (\'suggestion-discussion-\' + suggestion.suggestionId) : undefined }}" aria-label="{{ (suggestion.isExpanded ? \'Свернуть правку\' : \'Развернуть правку\') | t }}" > <i class="fas fa-chevron-down" aria-hidden="true"></i> </button> <button type="button" class="ds-btn-link btn-icon ds-btn-success" ng-click="$ctrl.acceptSuggestion(suggestion)" ng-if="$ctrl.canModerate()" ng-disabled="$ctrl.isSuggestionActionBlocked(suggestion)" aria-label="{{ \'Принять правку\' | t }}" > <i class="fas fa-check"></i> <span class="custom-tooltip">{{ \'Принять\' | t }}</span> </button> <button type="button" class="ds-btn-link btn-icon ds-btn-danger" ng-click="$ctrl.rejectSuggestion(suggestion)" ng-if="$ctrl.canModerate()" ng-disabled="$ctrl.isSuggestionActionBlocked(suggestion)" aria-label="{{ \'Отклонить правку\' | t }}" > <i class="fas fa-times" aria-hidden="true"></i> <span class="custom-tooltip">{{ \'Отклонить\' | t }}</span> </button> </div> </div> <p class="collab-write-uncertain" ng-if="$ctrl.suggestionDiscussionNotice(suggestion) && !suggestion.isExpanded" role="status" > <i class="fas fa-exclamation-triangle" aria-hidden="true"></i> {{ $ctrl.suggestionDiscussionNotice(suggestion) }} </p> <div class="suggestion-text" ng-if="suggestion.type === \'node-add\' || suggestion.type === \'node-delete\' || suggestion.type === \'node-update\'" > <strong class="suggestion-action-label" ng-if="suggestion.type === \'node-add\'" >{{ \'Добавить:\' | t }}</strong > <strong class="suggestion-action-label" ng-if="suggestion.type === \'node-delete\'" >{{ \'Удалить:\' | t }}</strong > <strong class="suggestion-action-label" ng-if="suggestion.type === \'node-update\'" >{{ \'Изменить:\' | t }}</strong > <span class="text-format">{{ suggestion.nodeDescription }}</span> <div class="suggestion-update-changes" ng-if="suggestion.updateChanges.length"> <div class="suggestion-update-change" ng-repeat="change in suggestion.updateChanges track by change.key" > <span class="suggestion-update-label">{{ change.label }}</span> <span class="text-delete" ng-if="change.before" >{{ change.before | limitTo:100 }}<span ng-if="change.before.length > 100" >…</span ></span > <span class="text-delete suggestion-update-empty" ng-if="!change.before" >{{ \'пусто\' | t }}</span > <span class="text-replace-arrow">→</span> <span class="text-add" ng-if="change.after" >{{ change.after | limitTo:100 }}<span ng-if="change.after.length > 100" >…</span ></span > <span class="text-add suggestion-update-empty" ng-if="!change.after" >{{ \'пусто\' | t }}</span > </div> </div> </div> <div class="suggestion-text" ng-if="suggestion.type === \'replace\'"> <strong class="suggestion-action-label">{{ \'Заменить:\' | t }}</strong> <div class="suggestion-replace-block"> <span class="text-delete" >{{ suggestion.deleteText | limitTo:100 }}<span ng-if="suggestion.deleteText.length > 100" >…</span ></span > <span class="text-replace-arrow">→</span> <span class="text-add" >{{ suggestion.addText | limitTo:100 }}<span ng-if="suggestion.addText.length > 100" >…</span ></span > </div> </div> <div class="suggestion-text" ng-if="suggestion.type === \'add\' || suggestion.type === \'delete\' || suggestion.type === \'format\'" > <strong class="suggestion-action-label" ng-if="suggestion.type === \'add\'" >{{ \'Добавить:\' | t }}</strong > <strong class="suggestion-action-label" ng-if="suggestion.type === \'delete\'" >{{ \'Удалить:\' | t }}</strong > <strong class="suggestion-action-label" ng-if="suggestion.type === \'format\'" >{{ \'Формат:\' | t }}</strong > <span ng-if="suggestion.type === \'add\'" class="text-add" >{{ suggestion.text | limitTo:100 }}<span ng-if="suggestion.text.length > 100" >…</span ></span > <span ng-if="suggestion.type === \'delete\'" class="text-delete" >{{ suggestion.text | limitTo:100 }}<span ng-if="suggestion.text.length > 100" >…</span ></span > <span ng-if="suggestion.type === \'format\'" class="text-format" >{{ suggestion.formatDescription }}</span > </div> <div class="replies-count-hint" ng-if="!suggestion.isExpanded && $ctrl.suggestionThreads[suggestion.suggestionId] && $ctrl.suggestionThreads[suggestion.suggestionId].length" ng-click="$event.stopPropagation(); $ctrl.toggleSuggestionExpand(suggestion)" > <i class="fas fa-reply" aria-hidden="true"></i> <span >{{ $ctrl.suggestionThreads[suggestion.suggestionId].length | pluralize:\'ответ\':\'ответа\':\'ответов\' }}</span > </div> <div id="suggestion-discussion-{{ ::suggestion.suggestionId }}" ng-if="suggestion.isExpanded" ng-click="$event.stopPropagation()" > <ng-include src="\'/pages/edit/editContentItem/partials/suggestion-replies.html\'" ></ng-include> </div> </div>
1 <button type="button" class="sidebar-show-more" ng-if="$ctrl.suggestions.length > $ctrl.suggestionsDisplayLimit" ng-click="$ctrl.showMoreSuggestions()" > {{ \'Показать ещё\' | t }} {{ $ctrl.suggestions.length - $ctrl.suggestionsDisplayLimit }} </button> </div> </div> </div> </div> <div class="tiptap-bottom-bar"> <div class="bottom-bar-left"></div> <div class="bottom-bar-right bottom-bar-save-status" aria-live="polite"> <span ng-show="$ctrl.isSaving" class="save-status save-status--saving"> <i class="fas fa-circle-notch fa-spin" aria-hidden="true"></i> {{ \'Сохранение…\' | t }} </span> <span ng-show="!$ctrl.isSaving && $ctrl.saveSuccess" class="save-status save-status--saved"> <i class="fas fa-check" aria-hidden="true"></i> {{ \'Сохранено\' | t }} </span> <span ng-show="!$ctrl.isSaving && !$ctrl.saveSuccess && $ctrl.hasUnsavedChanges" class="save-status save-status--unsaved" > {{ \'Не сохранено\' | t }} </span> </div> </div> </div> </div>'),s.put("/pages/edit/editContentItem/types/Image.html",'<ng-include src="\'/pages/edit/editContentItem/partials/expo-preview.html\'" ng-if="$ctrl.isPreviewMode" ></ng-include> <editor-header ng-if="$ctrl.showEditorHeader" doc-title="contentItem.title" slug="contentItem.slug" content-status="contentItem.status" content-link="contentItem.link" tiptap-editor="$ctrl.tiptapExpoEditor" is-preview-mode="$ctrl.isPreviewMode" is-saving="$ctrl.isSaving" is-publishing="$ctrl.isPublishing" is-save-error="$ctrl.isSaveError" has-unsaved-changes="$ctrl.hasUnsavedChanges" has-draft="$ctrl.hasDraft" can-edit="$ctrl.canEdit === true" on-mode-change="$ctrl.onEditorModeChange(mode)" on-save="contentItem.slug ? $ctrl.saveAndCreateVersion() : $ctrl.saveDraft()" on-save-version-dialog="$ctrl.openVersionNameDialog()" on-versions-click="$ctrl.toggleVersions()" is-saving-version="$ctrl.savingVersion" is-save-success="$ctrl.saveSuccess" last-version-saved-at="$ctrl.lastVersionSavedAt" on-publish="$ctrl.sendFormForPublish()" is-admin="$ctrl.isAdmin" on-open-settings="$ctrl.openPublishSettings()" on-run-typographer="$ctrl.applyTypography()" on-clean-code="$ctrl.cleanHtmlContent()" word-count="$ctrl.wordCount" char-count="$ctrl.charCount" initial-para-count="$ctrl.paraCount" is-logged-in="$ctrl.isLoggedIn" user-avatar="$ctrl.userAvatarId" user-path="$ctrl.userPathSlug" night-mode="$ctrl.darkMode" on-toggle-night-mode="$ctrl.toggleDarkMode()" ></editor-header> <div class="title-input-row tw:flex tw:flex-wrap" ng-hide="$ctrl.isPreviewMode"> <div class="title-col tw:sm:w-8/12 tw:lg:w-9/12"> <div class="form-group tw:font-bold title" ng-class="{\'has-error\': formErrors.title}"> <textarea class="ds-input big-textarea placeholder-big" placeholder="{{ \'Заголовок\' | t }}" ng-model="contentItem.title" ng-change="formErrors.title = false" maxlength="120" textarea-limit-rows="textarea-limit-rows" aria-label="{{ \'Заголовок изображения\' | t }}" rows="1" msd-elastic aria-invalid="{{ !!formErrors.title }}" aria-describedby="form-error-title" ></textarea> <span id="form-error-title" class="help-block" ng-show="formErrors.title" role="alert" >{{ \'Заголовок обязателен\' | t }}</span > </div> </div> <div class="cover-col tw:sm:w-4/12 tw:lg:w-3/12"> <div class="form-group"> <button class="big-button" type="button" ngf-select="$ctrl.uploadImage($file, 0)" ngf-drop="$ctrl.uploadImage($file, 0)" ngf-drag-over-class="\'drag-over\'" ngf-accept="\'image/*\'" ng-disabled="$ctrl.imageUploading[0]" ng-hide="contentItem.media[0].thumborId" > <p class="fa fa-paperclip fa-4x"></p> <div>{{ \'Загрузить обложку\' | t }}<br />(jpeg, png, gif)</div> </button> <div class="post-image-wrapper" ng-if="contentItem.media[0].thumborId"> <button type="button" class="close" ng-click="contentItem.media[0].thumborId = null;" aria-label="{{ \'Удалить изображение\' | t }}" > x </button> <img class="tw:block tw:max-w-full tw:h-auto thumbmail" ng-src="{{ contentItem.media[0].thumborId | imageUrl: 300 }}" alt="{{ contentItem.title || (\'Изображение\' | t) }}" /> </div> </div> </div> </div> <div class="form-group discours-editor big tiptap-mode" ng-show="!$ctrl.isPreviewMode" ng-class="{\'has-editor-header\': $ctrl.showEditorHeader }" >
1 <div class="tiptap-loading" ng-if="!$ctrl.tiptapReady"> <div class="loading-progress-bar"> <div class="loading-progress-fill"></div> </div> <div class="tiptap-loading-skeleton"> <div class="tiptap-loading-skeleton__line tiptap-loading-skeleton__line--60"></div> <div class="tiptap-loading-skeleton__gap"></div> <div class="tiptap-loading-skeleton__line tiptap-loading-skeleton__line--85"></div> <div class="tiptap-loading-skeleton__line tiptap-loading-skeleton__line--75"></div> </div> </div> <div ng-if="$ctrl.tiptapReady" class="tiptap-editor-container"> <div class="tiptap-editor-wrapper"> <ng-include ng-if="!$ctrl.commenterReadOnly" src="\'/pages/edit/editContentItem/partials/tiptap-toolbar.html\'" ></ng-include> <tiptap-editor ng-model="contentItem.media[0].body" placeholder="{{ \'Место для комментария — здесь можно рассказать о проекте, концепции, истории, технике, соавторах\' | t }}" editable="true" editor-instance="$ctrl.tiptapExpoEditor" content-item="contentItem" initial-content="contentItem.media[0].body" ></tiptap-editor> </div> <div class="tiptap-bottom-bar"> <div class="bottom-bar-left"></div> <div class="bottom-bar-right" ng-show="$ctrl.expoAutosaveStatus" aria-live="polite"> <span class="expo-autosave-indicator expo-autosave-indicator--{{ $ctrl.expoAutosaveStatus }}" > <i ng-if="$ctrl.expoAutosaveStatus === \'saving\'" class="fas fa-circle-notch fa-spin" aria-hidden="true" ></i> <i ng-if="$ctrl.expoAutosaveStatus === \'saved\'" class="fas fa-check" aria-hidden="true" ></i> <i ng-if="$ctrl.expoAutosaveStatus === \'error\'" class="fas fa-exclamation-triangle" aria-hidden="true" ></i> <i ng-if="$ctrl.expoAutosaveStatus === \'retrying\'" class="fas fa-sync fa-spin" aria-hidden="true" ></i> <span ng-if="$ctrl.expoAutosaveStatus === \'saving\'">{{ \'Сохранение...\' | t }}</span> <span ng-if="$ctrl.expoAutosaveStatus === \'saved\'">{{ \'Сохранено\' | t }}</span> <span ng-if="$ctrl.expoAutosaveStatus === \'error\'">{{ \'Ошибка сохранения\' | t }}</span> <span ng-if="$ctrl.expoAutosaveStatus === \'retrying\'">{{ \'Повтор...\' | t }}</span> </span> </div> </div> </div> </div>'),s.put("/pages/edit/editContentItem/types/ImageAlbum.html",'<ng-include src="\'/pages/edit/editContentItem/partials/expo-preview.html\'" ng-if="$ctrl.isPreviewMode" ></ng-include> <editor-header ng-if="$ctrl.showEditorHeader" doc-title="contentItem.title" slug="contentItem.slug" content-status="contentItem.status" content-link="contentItem.link" tiptap-editor="$ctrl.tiptapExpoEditor" is-preview-mode="$ctrl.isPreviewMode" is-saving="$ctrl.isSaving" is-publishing="$ctrl.isPublishing" is-save-error="$ctrl.isSaveError" has-unsaved-changes="$ctrl.hasUnsavedChanges" has-draft="$ctrl.hasDraft" can-edit="$ctrl.canEdit === true" on-mode-change="$ctrl.onEditorModeChange(mode)" on-save="contentItem.slug ? $ctrl.saveAndCreateVersion() : $ctrl.saveDraft()" on-save-version-dialog="$ctrl.openVersionNameDialog()" on-versions-click="$ctrl.toggleVersions()" is-saving-version="$ctrl.savingVersion" is-save-success="$ctrl.saveSuccess" last-version-saved-at="$ctrl.lastVersionSavedAt" on-publish="$ctrl.sendFormForPublish()" is-admin="$ctrl.isAdmin" on-open-settings="$ctrl.openPublishSettings()" on-run-typographer="$ctrl.applyTypography()" on-clean-code="$ctrl.cleanHtmlContent()" word-count="$ctrl.wordCount" char-count="$ctrl.charCount" initial-para-count="$ctrl.paraCount" is-logged-in="$ctrl.isLoggedIn" user-avatar="$ctrl.userAvatarId" user-path="$ctrl.userPathSlug" night-mode="$ctrl.darkMode" on-toggle-night-mode="$ctrl.toggleDarkMode()" ></editor-header> <div ng-hide="$ctrl.isPreviewMode"> <div> <div class="form-group tw:font-bold title" ng-class="{\'has-error\': formErrors.title}"> <textarea class="ds-input big-textarea placeholder-big" placeholder="{{ \'Название альбома\' | t }}" ng-model="contentItem.title" ng-change="formErrors.title = false" maxlength="120" textarea-limit-rows="textarea-limit-rows" rows="3" aria-label="{{ \'Название фотоальбома\' | t }}" aria-invalid="{{ !!formErrors.title }}" aria-describedby="form-error-title" ></textarea> <span id="form-error-title" class="help-block" ng-show="formErrors.title" role="alert" >{{ \'Заголовок обязателен\' | t }}</span > </div> </div> </div> <div class="media-row tw:flex tw:flex-wrap tw:mt-section" ng-repeat="mediaItem in contentItem.media track by $index" ng-if="!$
1ctrl.isPreviewMode" > <div class="media-thumb-col tw:sm:w-4/12 tw:lg:w-3/12"> <div class="form-group tw:relative media-item-actions"> <button class="big-button" type="button" ngf-select="$ctrl.uploadImage($file, $index)" ngf-drop="$ctrl.uploadImage($file, $index)" ngf-drag-over-class="\'drag-over\'" ngf-accept="\'image/*\'" ng-disabled="$ctrl.imageUploading[$index]" ng-hide="mediaItem.thumborId" > <p class="fa fa-paperclip fa-4x"></p> <div>{{ \'Загрузить изображение\' | t }}<br />(jpeg, png, gif)</div> <span class="fas fa-sync-alt fa-spin floating-text left" ng-show="$ctrl.imageUploading[$index]" ></span> </button> <img class="tw:block tw:max-w-full tw:h-auto thumbmail" ng-src="{{ mediaItem.thumborId | imageUrl: 300 }}" ng-show="mediaItem.thumborId" alt="{{ (contentItem.title || (\'Изображение\' | t)) + \', \' + ($index + 1) + \' / \' + contentItem.media.length }}" /> <button class="close" type="button" aria-label="{{ \'Удалить изображение\' | t }}" ng-click="$ctrl.deleteMediaItem($index);" ng-if="contentItem.media.length > 1" ng-hide="mediaItem.thumborId" > <span aria-hidden="true"><i class="fas fa-times"></i></span> </button> <button class="close white-bg" type="button" aria-label="{{ \'Удалить изображение\' | t }}" ng-click="mediaItem.thumborId = null;" ng-show="mediaItem.thumborId" > <span aria-hidden="true"><i class="fas fa-times"></i></span> </button> </div> <div class="tw:mt-section"></div> <div class="form-group"> <button class="ds-btn-border" type="button" ng-click="$ctrl.makeAlbumItemActive($index)" ng-class="{ active: mediaItem.main }" > <span ng-if="!mediaItem.main">{{ \'выбрать в качестве обложки\' | t }}</span ><span ng-if="mediaItem.main">{{ \'выбрано в качестве обложки\' | t }}</span> </button> </div> <div class="form-group" ng-if="$index === 0 && $ctrl.isAdmin && contentItem.media.length > 1"> <button class="ds-btn-border" type="button" ng-click="$ctrl.duplicateFirstMediaToAll()"> {{ \'Дублировать описание для всех изображений\' | t }} </button> </div> <div class="form-group media-reorder-buttons" ng-if="contentItem.media.length > 1"> <button class="ds-btn-border media-move-btn" type="button" ng-click="$ctrl.moveMediaItemUp($index)" ng-disabled="$index === 0" title="{{ \'Переместить вверх\' | t }}" > <i class="fas fa-arrow-up"></i> </button> <button class="ds-btn-border media-move-btn" type="button" ng-click="$ctrl.moveMediaItemDown($index)" ng-disabled="$index === contentItem.media.length - 1" title="{{ \'Переместить вниз\' | t }}" > <i class="fas fa-arrow-down"></i> </button> </div> </div> <div class="media-content-col tw:sm:w-8/12 tw:lg:w-9/12"> <div class="form-group tw:font-bold"> <textarea class="ds-input big-textarea placeholder-big auto-grow-title" placeholder="{{ \'Название работы\' | t }}" ng-model="mediaItem.title" rows="1" maxlength="140" msd-elastic aria-label="{{ \'Название работы в альбоме\' | t }}" ></textarea> </div> <div class="form-group discours-editor big tiptap-mode album-item-tiptap" ng-show="!$ctrl.isPreviewMode" ng-class="{\'has-editor-header\': $index === 0 && $ctrl.showEditorHeader }" > <tiptap-simple-editor ng-model="mediaItem.body" preset="album" placeholder="{{ \'Место для комментария — здесь можно рассказать о проекте, концепции, технике и истории создания...\' | t }}" ></tiptap-simple-editor> <div class="tiptap-bottom-bar"> <div class="bottom-bar-left"></div> <div class="bottom-bar-right" ng-show="$ctrl.expoAutosaveStatus" aria-live="polite"> <span class="expo-autosave-indicator expo-autosave-indicator--{{ $ctrl.expoAutosaveStatus }}" > <i ng-if="$ctrl.expoAutosaveStatus === \'saving\'" class="fas fa-circle-notch fa-spin" aria-hidden="true" ></i> <i ng-if="$ctrl.expoAutosaveStatus === \'saved\'" class="fas fa-check" aria-hidden="true" ></i> <i ng-if="$ctrl.expoAutosaveStatus === \'error\'" class="fas fa-exclamation-triangle" aria-hidden="true" ></i> <i ng-if="$ctrl.expoAutosaveStatus === \'retrying\'" class="fas fa-sync fa-spin" aria-hidden="true" ></i> <span ng-if="$ctrl.expoAutosaveStatus === \'saving\'">{{ \'Сохранение...\' | t }}</span> <span ng-if="$ctrl.expoAutosaveStatus === \'saved\'">{{ \'Сохранено\' | t }}</span> <span ng-if="$ctrl.expoAutosaveStatus === \'error\'">{{ \'Ошибка сохранения\' | t }}</span> <span ng-if="$ctrl.expoAutosaveStatus === \'retrying\'">{{ \'Повтор...\' | t }}</span> </span> </div> </div> </div> </div> </div>
1 <div class="tw:flex tw:flex-wrap tw:mb-section-lg" ng-if="!$ctrl.isPreviewMode"> <div class="tw:sm:w-4/12 tw:lg:w-3/12 discours-form"> <button class="ds-btn-border add-image-button" ng-click="contentItem.media.push({})" type="button" > <i class="fas fa-plus"></i>{{ \'добавить изображение\' | t }} </button> </div> </div>'),s.put("/pages/edit/editContentItem/types/Literature.html",'<div class="title-input-row tw:flex tw:flex-wrap" ng-hide="$ctrl.isPreviewMode"> <div class="title-col tw:sm:w-8/12 tw:lg:w-9/12"> <div class="form-group tw:font-bold title" ng-class="{\'has-error\': formErrors.title}"> <textarea class="ds-input big-textarea placeholder-big" placeholder="{{ \'Заголовок\' | t }}" ng-model="contentItem.title" ng-change="formErrors.title = false" maxlength="120" textarea-limit-rows="textarea-limit-rows" aria-label="{{ \'Заголовок литературного материала\' | t }}" rows="1" msd-elastic aria-invalid="{{ !!formErrors.title }}" aria-describedby="form-error-title" ></textarea> <span id="form-error-title" class="help-block" ng-show="formErrors.title" role="alert" >{{ \'Заголовок обязателен\' | t }}</span > </div> </div> <div class="cover-col tw:sm:w-4/12 tw:lg:w-3/12"> <div class="form-group"> <button class="big-button" type="button" ngf-select="$ctrl.uploadImage($file)" ngf-drop="$ctrl.uploadImage($file)" ngf-drag-over-class="\'drag-over\'" ngf-accept="\'image/*\'" ng-disabled="$ctrl.imageUploading[\'root\']" ng-hide="contentItem.thumborId" > <p class="fa fa-paperclip fa-4x"></p> <div>{{ \'Загрузить обложку\' | t }}<br />(jpeg, png, gif)</div> <span class="fas fa-sync-alt fa-spin floating-text left" ng-show="$ctrl.imageUploading[\'root\']" ></span> </button> <div class="post-image-wrapper" ng-if="contentItem.thumborId"> <div class="cover-actions cover-actions--desktop"> <button type="button" class="cover-action-btn" ng-click="$ctrl.cropCoverImage()" ng-disabled="$ctrl.cropUploading" aria-label="{{ \'Кадрировать обложку\' | t }}" data-tooltip="{{ \'Кадрировать\' | t }}" > <i class="fas fa-crop-alt" aria-hidden="true"></i> </button> <button type="button" class="cover-action-btn" ngf-select="$ctrl.uploadImage($file)" ngf-accept="\'image/*\'" ng-disabled="$ctrl.cropUploading || $ctrl.imageUploading[\'root\']" aria-label="{{ \'Заменить обложку\' | t }}" data-tooltip="{{ \'Заменить\' | t }}" > <i class="fas fa-image" aria-hidden="true"></i> </button> <button type="button" class="cover-action-btn cover-action-btn--delete" ng-click="$ctrl.deleteCoverImage()" ng-disabled="$ctrl.cropUploading" aria-label="{{ \'Удалить обложку\' | t }}" data-tooltip="{{ \'Удалить\' | t }}" > <i class="fas fa-times" aria-hidden="true"></i> </button> </div> <ng-include src="\'/pages/edit/editContentItem/partials/cover-actions-mobile.html\'" ></ng-include> <span class="fas fa-sync-alt fa-spin crop-upload-spinner" ng-show="$ctrl.cropUploading" ></span> <img class="tw:block tw:max-w-full tw:h-auto thumbmail" ng-class="{\'is-uploading\': $ctrl.cropUploading }" ng-src="{{ contentItem.thumborId | imageUrl: 300 }}" ng-click="$ctrl.cropCoverImage()" alt="{{ contentItem.imageCaption || (\'Обложка:\' | t) + \' \' + (contentItem.title || (\'материал\' | t)) + \' \' + (\'— Дискурс\' | t) }}" /> </div> <div class="image-caption-input"> <input type="text" name="imageCaption" placeholder="{{ \'Подпись изображения\' | t }}" class="material-style" ng-model="contentItem.imageCaption" /> <button type="button" class="caption-edit-icon" data-tooltip="{{ \'Редактировать подпись\' | t }}" ng-click="$ctrl.openImageCaptionEditor($event)" > <i class="fas fa-pencil-alt" aria-hidden="true"></i> </button> </div> </div> </div> </div> <editor-header ng-if="$ctrl.showEditorHeader" doc-title="contentItem.title" slug="contentItem.slug" content-status="contentItem.status" content-link="contentItem.link" collab-status="$ctrl.collabStatus" connected-users="$ctrl.connectedUsers" is-offline-mode="$ctrl.isOfflineMode" collab-permission-denied="$ctrl.collabPermissionDenied" is-reconnecting="$ctrl.isReconnecting" tiptap-editor="$ctrl.tiptapEditor" is-preview-mode="$ctrl.isPreviewMode" suggestion-mode="$ctrl.suggestionMode" sidebar-visible="$ctrl.sidebarVisible" pending-count="$ctrl.pendingChangesCount" collab-sync-pending="$ctrl.collabSyncPending" is-saving="$ctrl.isSaving" is-publishing="$ctrl.isPublishing" is-saving-version="$ctrl.savingVersion" is-save-success="$ctrl.saveSuccess" is-save-error="$ctrl.isSaveError" last-version-saved-at="$ctrl.lastVersionSavedAt" has-unsaved-changes="$ctrl.hasUnsavedChanges" has-draft="$ctrl.hasDraft" can-edit="$ctrl.canEdit === true" is-author="!$ctrl.contentItemCreatorId || $ctrl.userId === $ctrl.contentItemCreatorId" show-collab-control
1s="!!contentItem.slug" show-invite-button="$ctrl.canEdit === true && contentItem.slug" invite-count="$ctrl.inviteCount" on-open-invite="$ctrl.openInviteModal()" on-mode-change="$ctrl.onEditorModeChange(mode)" on-toggle-sidebar="$ctrl.toggleSidebar()" on-save="contentItem.slug ? $ctrl.saveAndCreateVersion() : $ctrl.saveDraft()" on-save-version-dialog="$ctrl.openVersionNameDialog()" on-publish="$ctrl.sendFormForPublish()" on-validate-before-publish="$ctrl.validateBeforePublish()" on-retry-connection="$ctrl.retryCollabConnection()" on-versions-click="$ctrl.toggleVersions()" night-mode="$ctrl.darkMode" on-toggle-night-mode="$ctrl.toggleDarkMode()" on-open-settings="$ctrl.openPublishSettings()" on-run-typographer="$ctrl.applyTypography()" word-count="$ctrl.wordCount" char-count="$ctrl.charCount" initial-para-count="$ctrl.paraCount" is-admin="$ctrl.isAdmin" on-clean-code="$ctrl.cleanHtmlContent()" initial-last-modified="$ctrl.initialLastModified" margin-view-mode="$ctrl.marginViewMode" on-toggle-margin-view="$ctrl.toggleMarginView()" quiet-mode="$ctrl.quietMode" on-toggle-quiet-mode="$ctrl.toggleQuietMode()" is-logged-in="$ctrl.isLoggedIn" user-avatar="$ctrl.userAvatarId" user-path="$ctrl.userPathSlug" ></editor-header> <invite-collaborators opened="$ctrl.inviteModalOpen" document-name="$ctrl.collabDocumentName" on-invites-changed="$ctrl.onInvitesChanged(invites)" ></invite-collaborators> <div class="post-preview tiptap-preview-mode lit-wrapper" ng-if="$ctrl.isPreviewMode && $ctrl.previewPost" post-images="post-images" footnote-text="footnote-text" social-text="social-text" > <div class="button-container"> <button type="button" class="preview-close-button" ng-click="$ctrl.togglePreviewMode()"> <svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg" > <path d="M17.9975 4.89363L13.1038 0L0 13.1064V18H4.89363L17.9975 4.89363ZM13.105 2.55674L15.4421 4.89383L14.4612 5.87591L12.1241 3.53882L13.105 2.55674ZM10.8453 4.81648L13.1824 7.15357L5.42174 14.9137L3.08485 12.5756L10.8453 4.81648ZM1.80725 16.1915V13.8544L4.14434 16.1915H1.80725Z" fill="currentColor" /> </svg> {{ \'Продолжить редактировать\' | t }} </button> </div> <div class="open-post progress expo-item"> <div class="author-category" ng-class="{\'no-author\': !$ctrl.previewPost.author }"> <span class="category" ng-if="$ctrl.previewPost.category"> <a ng-href="{{ $ctrl.previewPost.categoryLink }}" rel="noopener noreferrer" target="_blank" >{{ $ctrl.previewPost.category }}</a > </span> <span class="author" ng-if="$ctrl.previewPost.author"> <a ng-href="{{ $ctrl.previewPost.authorLink }}" rel="noopener noreferrer" target="_blank" ng-if="$ctrl.previewPost.authorLink" >{{ $ctrl.previewPost.author }}</a > <span ng-if="!$ctrl.previewPost.authorLink">{{ $ctrl.previewPost.author }}</span> </span> </div> <h1 class="title">{{ $ctrl.previewPost.title }}</h1> <div class="expo-item-title-image-wrapper" ng-if="$ctrl.previewPost.thumborId" ng-class="{\'is-image-loaded\': previewTitleImageLoaded}" > <img class="expo-item-title-image fancybox-img" fancybox-img ng-src="{{ $ctrl.previewPost.thumborId | imageUrl: 1000 }}" ng-srcset="{{ $ctrl.previewPost.thumborId | imageUrl: 720 }} 720w, {{ $ctrl.previewPost.thumborId | imageUrl: 900 }} 900w, {{ $ctrl.previewPost.thumborId | imageUrl: 1000 }} 1000w, {{ $ctrl.previewPost.thumborId | imageUrl: 1240 }} 1240w, {{ $ctrl.previewPost.thumborId | imageUrl: 1500 }} 1500w" fetchpriority="high" loading="eager" decoding="async" sizes="(min-width: 768px) 66vw, 100vw" alt="{{ $ctrl.previewPost.imageCaption ? ($ctrl.previewPost.imageCaption + \'. \' + $ctrl.previewPost.title + \' \' + (\'— Дискурс\' | t)) : (\'Обложка:\' | t) + \' \' + $ctrl.previewPost.title + \' \' + (\'— Дискурс\' | t) }}" title="{{ $ctrl.previewPost.imageCaption || ($ctrl.previewPost.title + \' \' + (\'— Дискурс\' | t)) }}" ng-image-onload="previewTitleImageLoaded = true" /> </div> <p class="image-description" ng-if="$ctrl.previewPost.imageCaption" ng-bind-html="$ctrl.previewPost.imageCaption | trustedHtml" ></p> <div class="lit" ng-bind-html="$ctrl.previewPost.body | trustedHtml" post-links="post-links" post-images="post-images" book="book" pdf-viewer-hydrate disclaimer="{{ $ctrl.previewPost.publishedAt }}" ></div> <div class="footer-post"> <hr class="tw:mt-2.5 tw:mb-5" /> <div class="post-meta-footer"> <div class="post-meta-footer__author"> <div class="author-title" ng-if="$ctrl.previewPost.author"> <a ng-href="{{ $ctrl.previewPost.authorLink }}" rel="noopener noreferrer" target="_blank" ng-if="$ctrl.previewPost.authorLink" >{{ $ctrl.previewPost.author }}</a >
1 <span ng-if="!$ctrl.previewPost.authorLink">{{ $ctrl.previewPost.author }}</span> </div> <div class="date-time"> <span class="time" ng-if="$ctrl.previewPost.publishedAt" >{{ $ctrl.previewPost.publishedAt | amDateFormat: \'D MMMM YYYY\' }}</span ><span class="time" ng-if="!$ctrl.previewPost.publishedAt && $ctrl.previewPost.postedAt" >{{ $ctrl.previewPost.postedAt | amDateFormat: \'D MMMM YYYY\' }}</span > </div> </div> </div> <ul class="tags tw:mt-2.5" ng-if="$ctrl.previewPost.tags.length"> <li ng-repeat="tag in $ctrl.previewPost.tags track by tag.slug"> <a ng-href="/tags/{{ tag.slug }}" rel="noopener noreferrer" target="_blank" >#{{ tag.title }}</a > </li> </ul> </div> </div> <div class="button-container bottom-button" ng-if="$ctrl.showBottomEditButton"> <button type="button" class="preview-close-button bottom" ng-click="$ctrl.togglePreviewMode()"> <svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg" > <path d="M17.9975 4.89363L13.1038 0L0 13.1064V18H4.89363L17.9975 4.89363ZM13.105 2.55674L15.4421 4.89383L14.4612 5.87591L12.1241 3.53882L13.105 2.55674ZM10.8453 4.81648L13.1824 7.15357L5.42174 14.9137L3.08485 12.5756L10.8453 4.81648ZM1.80725 16.1915V13.8544L4.14434 16.1915H1.80725Z" fill="currentColor" /> </svg> {{ \'Продолжить редактировать\' | t }} </button> </div> </div> <div class="form-group discours-editor lit-editor big tiptap-mode" ng-show="!$ctrl.isPreviewMode" ng-class="{\'has-editor-header\': $ctrl.showEditorHeader, \'quiet-mode\': $ctrl.quietMode }" > <div class="tiptap-loading" ng-if="!$ctrl.tiptapReady || $ctrl.collabSyncPending"> <div class="loading-progress-bar"> <div class="loading-progress-fill"></div> </div> <div class="tiptap-loading-skeleton"> <div class="tiptap-loading-skeleton__line"></div> <div class="tiptap-loading-skeleton__line tiptap-loading-skeleton__line--90"></div> <div class="tiptap-loading-skeleton__line tiptap-loading-skeleton__line--75"></div> <div class="tiptap-loading-skeleton__gap"></div> <div class="tiptap-loading-skeleton__line"></div> <div class="tiptap-loading-skeleton__line tiptap-loading-skeleton__line--85"></div> <div class="tiptap-loading-skeleton__line tiptap-loading-skeleton__line--60"></div> </div> </div> <div ng-if="$ctrl.commenterReadOnly && !$ctrl.loginForReviewAvailable && $ctrl.tiptapReady" class="backup-recovery-banner anonymous-suggestions-banner" > <div class="backup-message"> <i class="fas fa-comment"></i> <span>{{ \'Режим комментирования — выделите текст, чтобы оставить комментарий.\' | t }}</span> </div> </div> <div ng-if="$ctrl.loginForReviewAvailable && $ctrl.tiptapReady" class="backup-recovery-banner anonymous-suggestions-banner" > <div class="backup-message"> <i class="fas fa-key"></i> <span>{{ \'Войдите, чтобы оставлять правки в режиме рецензирования.\' | t }}</span> </div> <div class="backup-actions"> <button type="button" class="btn-restore" ng-click="$ctrl.openLoginModal()"> {{ \'Войти\' | t }} </button> </div> </div> <div ng-if="$ctrl.hasLocalBackup && contentItem.slug && !$ctrl.collabSyncPending" class="backup-recovery-banner" ng-class="{\'is-collapsed\': $ctrl.backupBannerCollapsed}" > <button type="button" ng-if="$ctrl.backupBannerCollapsed" class="backup-collapsed-indicator" ng-click="$ctrl.expandBackupBanner()" aria-label="{{ \'Показать резервную копию\' | t }}" title="{{ \'Показать резервную копию\' | t }}" > <i class="fas fa-history"></i> <span>{{ \'Есть резервная копия\' | t }}</span> </button> <div ng-if="!$ctrl.backupBannerCollapsed" class="backup-message"> <i class="fas fa-history"></i> <span >{{ \'Резервная копия от\' | t }} {{ $ctrl.localBackupTime | date:\'dd.MM.yyyy HH:mm\' }}. <span ng-bind="\'Восстановление заменит весь текст, в том числе правки соавторов. Текущий текст сначала сохранится версией.\' | t" ></span ></span> </div>
1 <div ng-if="!$ctrl.backupBannerCollapsed" class="backup-actions"> <button type="button" class="btn-restore" ng-click="$ctrl.restoreFromBackup()" ng-disabled="$ctrl.restoringBackup" > <i class="fas fa-undo"></i> {{ \'Восстановить\' | t }} </button> <button type="button" class="btn-discard" ng-click="$ctrl.discardBackup()"> <i class="fas fa-times" aria-hidden="true"></i> {{ \'Сохранить как версию\' | t }} </button> </div> </div> <div ng-if="$ctrl.tiptapReady" class="tiptap-editor-container" ng-class="{\'is-syncing\': $ctrl.collabSyncPending}" > <div class="toc-nav-container" id="toc-nav-container-literature"></div> <ng-include src="\'/pages/edit/editContentItem/partials/sidebar-side-toggle.html\'"></ng-include> <div class="tiptap-editor-wrapper"> <ng-include ng-if="!$ctrl.commenterReadOnly" src="\'/pages/edit/editContentItem/partials/tiptap-toolbar.html\'" ></ng-include> <div class="editor-with-margin-host" ng-class="{\'margin-view-active\': $ctrl.marginViewMode, \'sheet-open\': $ctrl.sidebarVisible }" > <tiptap-editor ng-model="contentItem.media[0].literatureBody" placeholder="{{ \'Текст\' | t }}" user-id="$ctrl.userId" user-name="$ctrl.userName" user-slug="$ctrl.userSlug" user-avatar="$ctrl.userAvatar" editor-instance="$ctrl.tiptapEditor" content-item="contentItem" collab-server-url="{{ $ctrl.collabServerUrl }}" collab-document-name="$ctrl.collabDocumentName" collab-token="$ctrl.collabToken" collab-status="$ctrl.collabStatus" collab-permission-denied="$ctrl.collabPermissionDenied" is-offline-mode="$ctrl.isOfflineMode" is-reconnecting="$ctrl.isReconnecting" on-collab-synced="$ctrl.onCollabSynced()" on-collab-fallback="$ctrl.onCollabFallback(reason)" on-initial-content-ready="$ctrl.onInitialContentReady()" initial-content="$ctrl.initialContent" ></tiptap-editor> <ng-include src="\'/pages/edit/editContentItem/partials/margin-view.html\'"></ng-include> </div> <div class="tiptap-sidebar" ng-class="{ visible: $ctrl.sidebarVisible }"> <button type="button" class="sheet-drag-handle" aria-label="{{ \'Изменить высоту панели\' | t }}" > <span class="sheet-drag-grip" aria-hidden="true"></span> </button> <div class="sidebar-header"> <div class="sidebar-tabs"> <button type="button" class="sidebar-tab" ng-class="{ active: $ctrl.sidebarTab === \'comments\' }" ng-click="$ctrl.sidebarTab = \'comments\'" data-collab-comments-tab > {{ \'Комментарии\' | t }} <span class="tab-count" ng-if="$ctrl.openCommentsCount" title="{{ \'Нерешённые комментарии\' | t }}" >({{ $ctrl.openCommentsCount }})</span > </button> <button type="button" class="sidebar-tab" ng-class="{ active: $ctrl.sidebarTab === \'suggestions\' }" ng-click="$ctrl.sidebarTab = \'suggestions\'" > {{ \'Правки\' | t }} <span class="tab-count" ng-if="$ctrl.suggestions.length" >({{ $ctrl.suggestions.length }})</span > </button> </div> <div class="sidebar-header-actions"> <div ng-if="!$ctrl.commentsLoading && $ctrl.sidebarTab === \'comments\'" ng-include="\'/pages/edit/editContentItem/partials/comment-filter.html\'" ></div> <button type="button" class="sidebar-close" ng-click="$ctrl.toggleSidebar()" title="{{ \'Закрыть\' | t }}" > <i class="fas fa-times" aria-hidden="true"></i> </button> </div> </div> <ng-include class="sidebar-capability-notice" ng-if="$ctrl.collabCommentStoreUnavailable" src="\'/pages/edit/editContentItem/partials/collab-capability-notice.html\'" ></ng-include> <div class="sidebar-content" ng-if="$ctrl.sidebarTab === \'comments\'"> <div ng-if="$ctrl.commentsLoading" class="comments-skeleton" aria-busy="true" aria-label="{{ \'Загрузка комментариев\' | t }}" > <div class="comments-skeleton__item" ng-repeat="i in [1,2,3] track by $index"> <div class="comments-skeleton__author"></div> <div class="comments-skeleton__text"></div> <div class="comments-skeleton__meta"></div> </div> </div> <div class="sidebar-empty" ng-if="!$ctrl.commentsLoading && !$ctrl.comments.length && !$ctrl.collabCommentStoreUnavailable" > <i class="fas fa-comment"></i> <p>{{ \'Нет комментариев\' | t }}</p> <small>{{ \'Выделите текст и нажмите кнопку комментария\' | t }}</small> </div>
1 <ng-include ng-if="!$ctrl.commentsLoading" src="\'/pages/edit/editContentItem/partials/comment-list.html\'" ></ng-include> </div> <div class="sidebar-content" ng-if="$ctrl.sidebarTab === \'suggestions\'"> <div class="sidebar-empty" ng-if="!$ctrl.suggestions.length"> <i class="fas fa-check-circle"></i> <p>{{ \'Нет ожидающих правок\' | t }}</p> <small>{{ \'Включите режим правок для отслеживания изменений\' | t }}</small> </div> <div class="suggestion-list" ng-if="$ctrl.suggestions.length"> <div class="suggestion-actions-all" ng-if="$ctrl.suggestions.length > 1 && $ctrl.canModerate()" > <button type="button" class="ds-btn ds-btn-sm ds-btn-success" ng-click="$ctrl.acceptAllSuggestions()" ng-disabled="$ctrl.isSuggestionBulkActionBlocked()" > <i class="fas fa-check-double"></i> {{ \'Принять все\' | t }} </button> <button type="button" class="ds-btn ds-btn-sm ds-btn-danger" ng-click="$ctrl.rejectAllSuggestions()" ng-disabled="$ctrl.isSuggestionBulkActionBlocked()" > <i class="fas fa-times" aria-hidden="true"></i> {{ \'Отклонить все\' | t }} </button> </div> <div class="suggestion-item" ng-repeat="suggestion in $ctrl.suggestions | limitTo:$ctrl.suggestionsDisplayLimit track by suggestion.suggestionId" ng-class="[suggestion.type, { expanded: suggestion.isExpanded }]" data-sidebar-suggestion-id="{{ suggestion.suggestionId }}" ng-click="$ctrl.toggleSuggestionExpand(suggestion)" role="group" ng-attr-aria-label="{{ ::(\'Правка от\' | t) + \' \' + (suggestion.authorName || (\'Автор\' | t)) }}" > <div class="suggestion-header"> <div class="suggestion-header-main"> <a class="suggestion-author" ng-href="/{{ suggestion.authorSlug }}" ng-if="suggestion.authorSlug" rel="noopener noreferrer" target="_blank" ng-click="$event.stopPropagation()" > <img class="author-avatar" ng-if="suggestion.authorAvatar" ng-src="{{ suggestion.authorAvatar }}" alt="" ng-on-error="$event.target.style.display=\'none\'" /> <span class="author-avatar-placeholder" ng-if="!suggestion.authorAvatar && suggestion.authorId" title="{{ suggestion.authorName || (\'Автор\' | t) }}" >{{ (suggestion.authorName || (\'А\' | t)).charAt(0) }}</span > <div class="sidebar-author-info"> <span class="author-name">{{ suggestion.authorName || (\'Автор\' | t) }}</span> <span class="sidebar-date" >{{ suggestion.createdAt | date:\'dd.MM HH:mm\' }}</span > </div> </a> <span class="suggestion-author" ng-if="!suggestion.authorSlug"> <img class="author-avatar" ng-if="suggestion.authorAvatar" ng-src="{{ suggestion.authorAvatar }}" alt="" ng-on-error="$event.target.style.display=\'none\'" /> <span class="author-avatar-placeholder" ng-if="!suggestion.authorAvatar && suggestion.authorId" title="{{ suggestion.authorName || (\'Автор\' | t) }}" >{{ (suggestion.authorName || (\'А\' | t)).charAt(0) }}</span > <div class="sidebar-author-info"> <span class="author-name" >{{ !suggestion.authorId ? (\'Анонимно\' | t) : (suggestion.authorName || (\'Автор\' | t)) }}</span > <span class="sidebar-date" >{{ suggestion.createdAt | date:\'dd.MM HH:mm\' }}</span > </div> </span> </div> <div class="suggestion-actions" ng-click="$event.stopPropagation()"> <button type="button" class="ds-btn-link btn-icon card-disclosure" ng-click="$ctrl.toggleSuggestionDisclosure(suggestion)" ng-attr-aria-expanded="{{ suggestion.isExpanded ? \'true\' : \'false\' }}" ng-attr-aria-controls="{{ suggestion.isExpanded ? (\'suggestion-discussion-\' + suggestion.suggestionId) : undefined }}" aria-label="{{ (suggestion.isExpanded ? \'Свернуть правку\' : \'Развернуть правку\') | t }}" > <i class="fas fa-chevron-down" aria-hidden="true"></i> </button> <button type="button" class="ds-btn-link btn-icon ds-btn-success" ng-click="$ctrl.acceptSuggestion(suggestion)" ng-if="$ctrl.canModerate()" ng-disabled="$ctrl.isSuggestionActionBlocked(suggestion)" aria-label="{{ \'Принять правку\' | t }}" > <i class="fas fa-check"></i> <span class="custom-tooltip">{{ \'Принять\' | t }}</span> </button> <button type="button" class="ds-btn-link btn-icon ds-btn-danger" ng-click="$ctrl.rejectSuggestion(suggestion)" ng-if="$ctrl.canModerate()" ng-disabled="$ctrl.isSuggestionActionBlocked(suggestion)" aria-label="{{ \'Отклонить правку\' | t }}" > <i class="fas fa-times" aria-hidden="true"></i> <span class="custom-tooltip">{{ \'Отклонить\' | t }}</span> </button> </div> </div> <p class="collab-write-uncertain" ng-if="$ctrl.suggestionDiscussionNotice(suggestion) && !suggestion.isExpanded" role="status" > <i class="fas fa-exclamation-triangle" aria-hidden="true"></i> {{ $ctrl.suggestionDiscussionNotice(suggestion) }} </p> <div class="suggestion-text" ng-if="suggestion.type === \'node-add\' || suggestion.type === \'node-delete\' || suggestion.type === \'node-update\'" > <strong class="suggestion-action-label" ng-if="suggestion.type === \'node-add\'" >{{ \'Добавить:\' | t }}</strong > <strong class="suggestion-action-label" ng-if="suggestion.type === \'node-delete\'" >{{ \'Удалить:\' | t }}</strong > <strong class="suggestion-action-label" ng-if="suggestion.type === \'node-update\'" >{{ \'Изменить:\' | t }}</strong > <span class="text-format">{{ suggestion.nodeDescription }}</span> <div class="suggestion-update-changes" ng-if="suggestion.updateChanges.length"> <div class="suggestion-update-change" ng-repeat="change in suggestion.updateChanges track by change.key" > <span class="suggestion-update-label">{{ change.label }}</span> <span class="text-delete" ng-if="change.before" >{{ change.before | limitTo:100 }}<span ng-if="change.before.length > 100" >…</span ></span > <span class="text-delete suggestion-update-empty" ng-if="!change.before" >{{ \'пусто\' | t }}</span > <span class="text-replace-arrow">→</span> <span class="text-add" ng-if="change.after" >{{ change.after | limitTo:100 }}<span ng-if="change.after.length > 100" >…</span ></span > <span class="text-add suggestion-update-empty" ng-if="!change.after" >{{ \'пусто\' | t }}</span > </div> </div> </div> <div class="suggestion-text" ng-if="suggestion.type === \'replace\'"> <strong class="suggestion-action-label">{{ \'Заменить:\' | t }}</strong> <div class="suggestion-replace-block"> <span class="text-delete" >{{ suggestion.deleteText | limitTo:100 }}<span ng-if="suggestion.deleteText.length > 100" >…</span ></span > <span class="text-replace-arrow">→</span> <span class="text-add" >{{ suggestion.addText | limitTo:100 }}<span ng-if="suggestion.addText.length > 100" >…</span ></span > </div> </div> <div class="suggestion-text" ng-if="suggestion.type === \'add\' || suggestion.type === \'delete\' || suggestion.type === \'format\'" > <strong class="suggestion-action-label" ng-if="suggestion.type === \'add\'" >{{ \'Добавить:\' | t }}</strong > <strong class="suggestion-action-label" ng-if="suggestion.type === \'delete\'" >{{ \'Удалить:\' | t }}</strong > <strong class="suggestion-action-label" ng-if="suggestion.type === \'format\'" >{{ \'Формат:\' | t }}</strong > <span ng-if="suggestion.type === \'add\'" class="text-add" >{{ suggestion.text | limitTo:100 }}<span ng-if="suggestion.text.length > 100" >…</span ></span > <span ng-if="suggestion.type === \'delete\'" class="text-delete" >{{ suggestion.text | limitTo:100 }}<span ng-if="suggestion.text.length > 100" >…</span ></span > <span ng-if="suggestion.type === \'format\'" class="text-format" >{{ suggestion.formatDescription }}</span > </div> <div class="replies-count-hint" ng-if="!suggestion.isExpanded && $ctrl.suggestionThreads[suggestion.suggestionId] && $ctrl.suggestionThreads[suggestion.suggestionId].length" ng-click="$event.stopPropagation(); $ctrl.toggleSuggestionExpand(suggestion)" > <i class="fas fa-reply" aria-hidden="true"></i> <span >{{ $ctrl.suggestionThreads[suggestion.suggestionId].length | pluralize:\'ответ\':\'ответа\':\'ответов\' }}</span > </div> <div id="suggestion-discussion-{{ ::suggestion.suggestionId }}" ng-if="suggestion.isExpanded" ng-click="$event.stopPropagation()" > <ng-include src="\'/pages/edit/editContentItem/partials/suggestion-replies.html\'" ></ng-include> </div> </div>
1 <button type="button" class="sidebar-show-more" ng-if="$ctrl.suggestions.length > $ctrl.suggestionsDisplayLimit" ng-click="$ctrl.showMoreSuggestions()" > {{ \'Показать ещё\' | t }} {{ $ctrl.suggestions.length - $ctrl.suggestionsDisplayLimit }} </button> </div> </div> </div> </div> <div class="tiptap-bottom-bar"> <div class="bottom-bar-left"></div> <div class="bottom-bar-right bottom-bar-save-status" aria-live="polite"> <span ng-show="$ctrl.isSaving" class="save-status save-status--saving"> <i class="fas fa-circle-notch fa-spin" aria-hidden="true"></i> {{ \'Сохранение…\' | t }} </span> <span ng-show="!$ctrl.isSaving && $ctrl.saveSuccess" class="save-status save-status--saved"> <i class="fas fa-check" aria-hidden="true"></i> {{ \'Сохранено\' | t }} </span> <span ng-show="!$ctrl.isSaving && !$ctrl.saveSuccess && $ctrl.hasUnsavedChanges" class="save-status save-status--unsaved" > {{ \'Не сохранено\' | t }} </span> </div> </div> </div> </div>'),s.put("/pages/edit/editContentItem/types/Music.html",'<ng-include src="\'/pages/edit/editContentItem/partials/expo-preview.html\'" ng-if="$ctrl.isPreviewMode" ></ng-include> <editor-header ng-if="$ctrl.showEditorHeader" doc-title="contentItem.title" slug="contentItem.slug" content-status="contentItem.status" content-link="contentItem.link" tiptap-editor="$ctrl.tiptapExpoEditor" is-preview-mode="$ctrl.isPreviewMode" is-saving="$ctrl.isSaving" is-publishing="$ctrl.isPublishing" is-save-error="$ctrl.isSaveError" has-unsaved-changes="$ctrl.hasUnsavedChanges" has-draft="$ctrl.hasDraft" can-edit="$ctrl.canEdit === true" on-mode-change="$ctrl.onEditorModeChange(mode)" on-save="contentItem.slug ? $ctrl.saveAndCreateVersion() : $ctrl.saveDraft()" on-save-version-dialog="$ctrl.openVersionNameDialog()" on-versions-click="$ctrl.toggleVersions()" is-saving-version="$ctrl.savingVersion" is-save-success="$ctrl.saveSuccess" last-version-saved-at="$ctrl.lastVersionSavedAt" on-publish="$ctrl.sendFormForPublish()" is-admin="$ctrl.isAdmin" on-open-settings="$ctrl.openPublishSettings()" on-run-typographer="$ctrl.applyTypography()" on-clean-code="$ctrl.cleanHtmlContent()" word-count="$ctrl.wordCount" char-count="$ctrl.charCount" initial-para-count="$ctrl.paraCount" is-logged-in="$ctrl.isLoggedIn" user-avatar="$ctrl.userAvatarId" user-path="$ctrl.userPathSlug" night-mode="$ctrl.darkMode" on-toggle-night-mode="$ctrl.toggleDarkMode()" ></editor-header> <div class="title-input-row tw:flex tw:flex-wrap" ng-hide="$ctrl.isPreviewMode"> <div class="title-col tw:sm:w-8/12 tw:lg:w-9/12"> <div class="form-group tw:font-bold title" ng-class="{\'has-error\': formErrors.title}"> <textarea class="ds-input big-textarea placeholder-big" placeholder="{{ \'Название композиции\' | t }}" ng-model="contentItem.title" ng-change="formErrors.title = false" maxlength="120" textarea-limit-rows="textarea-limit-rows" aria-label="{{ \'Название композиции\' | t }}" rows="1" msd-elastic aria-invalid="{{ !!formErrors.title }}" aria-describedby="form-error-title" ></textarea> <span id="form-error-title" class="help-block" ng-show="formErrors.title" role="alert" >{{ \'Заголовок обязателен\' | t }}</span > </div> <div class="form-group"> <input class="ds-input" ng-model="contentItem.media[0].performer" placeholder="{{ \'Имя исполнителя\' | t }}" /> </div> </div> <div class="cover-col tw:sm:w-4/12 tw:lg:w-3/12"> <div class="form-group"> <button class="big-button thin" type="button" ngf-select="$ctrl.uploadImage($file)" ngf-drop="$ctrl.uploadImage($file)" ngf-drag-over-class="\'drag-over\'" ngf-accept="\'image/*\'" ng-disabled="$ctrl.imageUploading[\'root\']" ng-hide="contentItem.thumborId" > <img src="/images/icons/attach.png" alt="{{ \'Загрузить\' | t }}" /><span >{{ \'Добавить обложку\' | t }}</span ><span class="fas fa-sync-alt fa-spin floating-text left" ng-show="$ctrl.imageUploading[\'root\']" ></span ><span class="floating-text">*jpeg, png, gif</span> </button> <div class="post-image-wrapper" ng-if="contentItem.thumborId"> <div class="cover-actions cover-actions--desktop"> <button type="button" class="cover-action-btn" ng-click="$ctrl.cropCoverImage()" ng-disabled="$ctrl.cropUploading" aria-label="{{ \'Кадрировать обложку\' | t }}" data-tooltip="{{ \'Кадрировать\' | t }}" > <i class="fas fa-crop-alt" aria-hidden="true"></i> </button> <button type="button" class="cover-action-btn" ngf-select="$ctrl.uploadImage($file)" ngf-accept="\'image/*\'" ng-disabled="$ctrl.cropUploading || $ctrl.imageUploading[\'root\']" aria-label="{{ \'Заменить обложку\' | t }}" data-tooltip="{{ \'Заменить\' | t }}" > <i class="fas fa-image" aria-hidden="true"></i> </button> <button type="button" class="cover-action-btn cover-action-btn--delete" ng-click="$ctrl.deleteCoverImage()" ng-disabled="$ctrl.cropUploading" aria-label="{{ \'Удалить обложку\' | t }}" data-tooltip="{{ \'Удалить\' | t }}" > <i class="fas fa-times" aria-hidden="true"></i> </button> </div> <ng-include src="\'/pages/edit/editContentItem/partials/cover-actions-mobile.html\'" ></ng-include> <span class="fas fa-sync-alt fa-spin crop-upload-spinner" ng-show="$ctrl.cropUploading" ></span> <img class="tw:block tw:max-w-full tw:h-auto thumbmail" ng-class="{\'is-uploading\': $ctrl.cropUploading }" ng-src="{{ contentItem.thumborId | imageUrl: 300 }}" ng-click="$ctrl.cropCoverImage()" alt="{{ (\'Обложка:\' | t) + \' \' + (contentItem.title || (\'композиция\' | t)) + \' — Дискурс\' }}" /> </div> </div> </div> <div class="music-upload-col tw:sm:w-4/12 tw:lg:w-3/12"> <div class="form-group">
1 <button class="big-button thin small" type="button" ngf-select="$ctrl.uploadMusic($file, 0)" ng-disabled="$ctrl.musicUploading[0]" ng-hide="contentItem.media[0].fileUrl" accept="audio/*" > <img src="/images/icons/attach.png" alt="{{ \'Загрузить\' | t }}" /><span >{{ \'Загрузить композицию\' | t }}</span ><span class="fas fa-sync-alt fa-spin floating-text left" ng-show="$ctrl.musicUploading[0]" ></span ><span class="floating-text">*mp3, wav, m4a, ogg, flac</span> </button> <div ng-show="contentItem.media[0].fileName"> <p>{{ \'Имя файла:\' | t }}</p> <p> {{ contentItem.media[0].fileName }}<i class="fas fa-times tw:cursor-pointer" ng-click="contentItem.media[0].fileName = null; contentItem.media[0].fileUrl = null;" ></i> </p> </div> </div> </div> </div> <div class="form-group discours-editor big tiptap-mode" ng-show="!$ctrl.isPreviewMode" ng-class="{\'has-editor-header\': $ctrl.showEditorHeader }" > <div class="tiptap-loading" ng-if="!$ctrl.tiptapReady"> <div class="loading-progress-bar"> <div class="loading-progress-fill"></div> </div> <div class="tiptap-loading-skeleton"> <div class="tiptap-loading-skeleton__line tiptap-loading-skeleton__line--60"></div> <div class="tiptap-loading-skeleton__gap"></div> <div class="tiptap-loading-skeleton__line tiptap-loading-skeleton__line--85"></div> <div class="tiptap-loading-skeleton__line tiptap-loading-skeleton__line--75"></div> </div> </div> <div ng-if="$ctrl.tiptapReady" class="tiptap-editor-container"> <div class="tiptap-editor-wrapper"> <ng-include ng-if="!$ctrl.commenterReadOnly" src="\'/pages/edit/editContentItem/partials/tiptap-toolbar.html\'" ></ng-include> <tiptap-editor ng-model="contentItem.body" placeholder="{{ \'Текст\' | t }}" editable="true" editor-instance="$ctrl.tiptapExpoEditor" content-item="contentItem" initial-content="contentItem.body" ></tiptap-editor> </div> <div class="tiptap-bottom-bar"> <div class="bottom-bar-left"></div> <div class="bottom-bar-right" ng-show="$ctrl.expoAutosaveStatus" aria-live="polite"> <span class="expo-autosave-indicator expo-autosave-indicator--{{ $ctrl.expoAutosaveStatus }}" > <i ng-if="$ctrl.expoAutosaveStatus === \'saving\'" class="fas fa-circle-notch fa-spin" aria-hidden="true" ></i> <i ng-if="$ctrl.expoAutosaveStatus === \'saved\'" class="fas fa-check" aria-hidden="true" ></i> <i ng-if="$ctrl.expoAutosaveStatus === \'error\'" class="fas fa-exclamation-triangle" aria-hidden="true" ></i> <i ng-if="$ctrl.expoAutosaveStatus === \'retrying\'" class="fas fa-sync fa-spin" aria-hidden="true" ></i> <span ng-if="$ctrl.expoAutosaveStatus === \'saving\'">{{ \'Сохранение...\' | t }}</span> <span ng-if="$ctrl.expoAutosaveStatus === \'saved\'">{{ \'Сохранено\' | t }}</span> <span ng-if="$ctrl.expoAutosaveStatus === \'error\'">{{ \'Ошибка сохранения\' | t }}</span> <span ng-if="$ctrl.expoAutosaveStatus === \'retrying\'">{{ \'Повтор...\' | t }}</span> </span> </div> </div> </div> </div>'),s.put("/pages/edit/editContentItem/types/MusicAlbum.html",'<ng-include src="\'/pages/edit/editContentItem/partials/expo-preview.html\'" ng-if="$ctrl.isPreviewMode" ></ng-include> <editor-header ng-if="$ctrl.showEditorHeader" doc-title="contentItem.title" slug="contentItem.slug" content-status="contentItem.status" content-link="contentItem.link" tiptap-editor="$ctrl.tiptapExpoEditor" is-preview-mode="$ctrl.isPreviewMode" is-saving="$ctrl.isSaving" is-publishing="$ctrl.isPublishing" is-save-error="$ctrl.isSaveError" has-unsaved-changes="$ctrl.hasUnsavedChanges" has-draft="$ctrl.hasDraft" can-edit="$ctrl.canEdit === true" on-mode-change="$ctrl.onEditorModeChange(mode)" on-save="contentItem.slug ? $ctrl.saveAndCreateVersion() : $ctrl.saveDraft()" on-save-version-dialog="$ctrl.openVersionNameDialog()" on-versions-click="$ctrl.toggleVersions()" is-saving-version="$ctrl.savingVersion" is-save-success="$ctrl.saveSuccess" last-version-saved-at="$ctrl.lastVersionSavedAt" on-publish="$ctrl.sendFormForPublish()" is-admin="$ctrl.isAdmin" on-open-settings="$ctrl.openPublishSettings()" on-run-typographer="$ctrl.applyTypography()" on-clean-code="$ctrl.cleanHtmlContent()" word-count="$ctrl.wordCount" char-count="$ctrl.charCount" initial-para-count="$ctrl.paraCount" is-logged-in="$ctrl.isLoggedIn" user-avatar="$ctrl.userAvatarId" user-path="$ctrl.userPathSlug" night-mode="$ctrl.darkMode" on-toggle-night-mode="$ctrl.toggleDarkMode()" ></editor-header> <div class="title-input-row tw:flex tw:flex-wrap" ng-hide="$ctrl.isPreviewMode"> <div class="title-col tw:sm:w-8/12 tw:lg:w-9/12"> <div class="form-group tw:font-bold title" ng-class="{\'has-error\': formErrors.title}"> <textarea class="ds-input big-textarea placeholder-big" placeholder="{{ \'Название альбома\' | t }}" ng-model="contentItem.title" ng-change="formErrors.title = false" maxlength="120" textarea-limit-rows="textarea-limit-rows" aria-label="{{ \'Название музыкального альбома\' | t }}" rows="1" msd-elastic aria-invalid="{{ !!formErrors.title }}" aria-describedby="form-error-title" ></textarea> <span id="form-error-title" class="help-block" ng-show="formErrors.title" role="alert" >{{ \'Заголовок обязателен\' | t }}</span > </div> </div> <div class="cover-col tw:sm:w-4/12 tw:lg:w-3/12"> <div class="form-group"> <button class="big-button" type="button" ngf-select="$ctrl.uploadImage($file)" ngf-drop="$ctrl.uploadImage($file)" ngf-drag-over-class="\'drag-over\'" ngf-accept="\'image/*\'" ng-disabled="$ctrl.imageUploading[\'root\']" ng-hide="contentItem.thumborId" > <p class="fa fa-paperclip fa-4x"></p> <div>{{ \'Загрузить обложку\' | t }}<br />(jpeg, png, gif)</div> <span class="fas fa-sync-alt fa-spin floating-text left" ng-show="$ctrl.imageUploading[\'root\']" ></span> </button> <div class="post-image-wrapper" ng-if="contentItem.thumborId"> <div class="cover-actions cover-actions--desktop"> <button type="button" class="cover-action-btn" ng-click="$ctrl.cropCoverImage()" ng-disabled="$ctrl.cropUploading" aria-label="{{ \'Кадрировать обложку\' | t }}" data-tooltip="{{ \'Кадрировать\' | t }}" > <i class="fas fa-crop-alt" aria-hidden="true"></i> </button> <button type="button" class="cover-action-btn" ngf-select="$ctrl.uploadImage($file)" ngf-accept="\'image/*\'" ng-disabled="$ctrl.cropUploading || $ctrl.imageUploading[\'root\']" aria-label="{{ \'Заменить обложку\' | t }}" data-tooltip="{{ \'Заменить\' | t }}" > <i class="fas fa-image" aria-hidden="true"></i> </button> <button type="button" class="cover-action-btn cover-action-btn--delete" ng-click="$ctrl.deleteCoverImage()" ng-disabled="$ctrl.cropUploading" aria-label="{{ \'Удалить обложку\' | t }}" data-tooltip="{{ \'Удалить\' | t }}" > <i class="fas fa-times" aria-hidden="true"></i> </button> </div> <ng-include src="\'/pages/edit/editContentItem/partials/cover-actions-mobile.html\'" ></ng-include> <span class="fas fa-sync-alt fa-spin crop-upload-spinner" ng-show="$ctrl.cropUploading" ></span> <img class="tw:block tw:max-w-full tw:h-auto thumbmail" ng-class="{\'is-uploading\': $ctrl.cropUploading }" ng-src="{{ contentItem.thumborId | imageUrl: 300 }}" ng-click="$ctrl.cropCoverImage()" alt="{{ (\'Обложка:\' | t) + \' \' + (contentItem.title || (\'альбом\' | t)) + \' — Дискурс\' }}" /> </div> </div> </div> </div> <div> <div class="form-group discours-editor big tiptap-mode" ng-show="!$ctrl.isPreviewMode" ng-class="{\'has-editor-header\': $ctrl.showEditorHeader }" >
1 <div class="tiptap-loading" ng-if="!$ctrl.tiptapReady"> <div class="loading-progress-bar"> <div class="loading-progress-fill"></div> </div> <div class="tiptap-loading-skeleton"> <div class="tiptap-loading-skeleton__line tiptap-loading-skeleton__line--60"></div> <div class="tiptap-loading-skeleton__gap"></div> <div class="tiptap-loading-skeleton__line tiptap-loading-skeleton__line--85"></div> <div class="tiptap-loading-skeleton__line tiptap-loading-skeleton__line--75"></div> </div> </div> <div ng-if="$ctrl.tiptapReady" class="tiptap-editor-container"> <div class="tiptap-editor-wrapper"> <ng-include ng-if="!$ctrl.commenterReadOnly" src="\'/pages/edit/editContentItem/partials/tiptap-toolbar.html\'" ></ng-include> <tiptap-editor ng-model="contentItem.body" placeholder="{{ \'Текст\' | t }}" editable="true" editor-instance="$ctrl.tiptapExpoEditor" content-item="contentItem" initial-content="contentItem.body" ></tiptap-editor> </div> <div class="tiptap-bottom-bar"> <div class="bottom-bar-left"></div> <div class="bottom-bar-right" ng-show="$ctrl.expoAutosaveStatus" aria-live="polite"> <span class="expo-autosave-indicator expo-autosave-indicator--{{ $ctrl.expoAutosaveStatus }}" > <i ng-if="$ctrl.expoAutosaveStatus === \'saving\'" class="fas fa-circle-notch fa-spin" aria-hidden="true" ></i> <i ng-if="$ctrl.expoAutosaveStatus === \'saved\'" class="fas fa-check" aria-hidden="true" ></i> <i ng-if="$ctrl.expoAutosaveStatus === \'error\'" class="fas fa-exclamation-triangle" aria-hidden="true" ></i> <i ng-if="$ctrl.expoAutosaveStatus === \'retrying\'" class="fas fa-sync fa-spin" aria-hidden="true" ></i> <span ng-if="$ctrl.expoAutosaveStatus === \'saving\'">{{ \'Сохранение...\' | t }}</span> <span ng-if="$ctrl.expoAutosaveStatus === \'saved\'">{{ \'Сохранено\' | t }}</span> <span ng-if="$ctrl.expoAutosaveStatus === \'error\'">{{ \'Ошибка сохранения\' | t }}</span> <span ng-if="$ctrl.expoAutosaveStatus === \'retrying\'">{{ \'Повтор...\' | t }}</span> </span> </div> </div> </div> </div> <div class="media-row tw:flex tw:flex-wrap tw:mt-section" ng-repeat="mediaItem in contentItem.media track by $index" > <div class="media-thumb-col tw:sm:w-5/12 tw:lg:w-3/12"> <div class="form-group tw:relative media-item-actions"> <p> <b>{{ \'Трек\' | t }} {{ \'#\' }}{{ $index + 1 }}</b> </p>
1 <button class="big-button thin small" type="button" ngf-select="$ctrl.uploadMusic($file, $index)" ng-disabled="$ctrl.musicUploading[$index]" ng-hide="mediaItem.fileUrl" accept="audio/*" > <p class="fa fa-paperclip fa-2x"></p> <div>{{ \'Загрузить композицию\' | t }}<br />(mp3, wav, m4a, ogg, flac)</div> <span class="fas fa-sync-alt fa-spin floating-text left" ng-show="$ctrl.musicUploading[$index]" ></span> </button> <div ng-show="mediaItem.fileUrl"> <p>{{ \'Имя файла:\' | t }}</p> <p> {{ mediaItem.fileName }}<i class="fas fa-times tw:cursor-pointer" ng-click="contentItem.media.splice($index, 1)" ></i> </p> </div> </div> </div> <div class="media-content-col tw:sm:w-7/12 tw:lg:w-9/12 tw:mt-10"> <div class="form-group"> <p> <input class="ds-input" placeholder="{{ \'Название записи\' | t }}" ng-model="mediaItem.title" required="required" maxlength="120" /> </p> <input class="ds-input" ng-model="mediaItem.performer" placeholder="{{ \'Исполнитель\' | t }}" /> </div> </div> </div> <div class="tw:flex tw:flex-wrap tw:mb-section-lg"> <div class="tw:sm:w-5/12 tw:lg:w-3/12 discours-form"> <button class="ds-btn-border add-image-button" ng-click="contentItem.media.push({})" type="button" > <i class="fas fa-plus"></i>{{ \'добавить композицию\' | t }} </button> </div> </div> </div>'),s.put("/pages/edit/editContentItem/types/Video.html",'<ng-include src="\'/pages/edit/editContentItem/partials/expo-preview.html\'" ng-if="$ctrl.isPreviewMode" ></ng-include> <editor-header ng-if="$ctrl.showEditorHeader" doc-title="contentItem.title" slug="contentItem.slug" content-status="contentItem.status" content-link="contentItem.link" tiptap-editor="$ctrl.tiptapExpoEditor" is-preview-mode="$ctrl.isPreviewMode" is-saving="$ctrl.isSaving" is-publishing="$ctrl.isPublishing" is-save-error="$ctrl.isSaveError" has-unsaved-changes="$ctrl.hasUnsavedChanges" has-draft="$ctrl.hasDraft" can-edit="$ctrl.canEdit === true" on-mode-change="$ctrl.onEditorModeChange(mode)" on-save="contentItem.slug ? $ctrl.saveAndCreateVersion() : $ctrl.saveDraft()" on-save-version-dialog="$ctrl.openVersionNameDialog()" on-versions-click="$ctrl.toggleVersions()" is-saving-version="$ctrl.savingVersion" is-save-success="$ctrl.saveSuccess" last-version-saved-at="$ctrl.lastVersionSavedAt" on-publish="$ctrl.sendFormForPublish()" is-admin="$ctrl.isAdmin" on-open-settings="$ctrl.openPublishSettings()" on-run-typographer="$ctrl.applyTypography()" on-clean-code="$ctrl.cleanHtmlContent()" word-count="$ctrl.wordCount" char-count="$ctrl.charCount" initial-para-count="$ctrl.paraCount" is-logged-in="$ctrl.isLoggedIn" user-avatar="$ctrl.userAvatarId" user-path="$ctrl.userPathSlug" night-mode="$ctrl.darkMode" on-toggle-night-mode="$ctrl.toggleDarkMode()" ></editor-header> <div class="title-input-row tw:flex tw:flex-wrap" ng-hide="$ctrl.isPreviewMode"> <div class="title-col tw:sm:w-8/12 tw:lg:w-9/12"> <div class="form-group tw:font-bold title" ng-class="{\'has-error\': formErrors.title}"> <textarea class="ds-input big-textarea placeholder-big" placeholder="{{ \'Заголовок\' | t }}" ng-model="contentItem.title" ng-change="formErrors.title = false" maxlength="120" textarea-limit-rows="textarea-limit-rows" aria-label="{{ \'Заголовок видео\' | t }}" rows="1" msd-elastic aria-invalid="{{ !!formErrors.title }}" aria-describedby="form-error-title" ></textarea> <span id="form-error-title" class="help-block" ng-show="formErrors.title" role="alert" >{{ \'Заголовок обязателен\' | t }}</span > </div> </div> <div class="cover-col tw:sm:w-4/12 tw:lg:w-3/12 tw:mb-2.5"> <div class="form-group"> <button class="big-button" type="button" ngf-select="$ctrl.uploadImage($file)" ngf-drop="$ctrl.uploadImage($file)" ngf-drag-over-class="\'drag-over\'" ngf-accept="\'image/*\'" ng-disabled="$ctrl.imageUploading[\'root\']" ng-hide="contentItem.thumborId" > <p class="fa fa-paperclip fa-4x"></p> <div>{{ \'Загрузить обложку\' | t }}<br />(jpeg, png, gif)</div> <span class="fas fa-sync-alt fa-spin floating-text left" ng-show="$ctrl.imageUploading[\'root\']" ></span> </button> <div class="post-image-wrapper" ng-if="contentItem.thumborId"> <div class="cover-actions cover-actions--desktop"> <button type="button" class="cover-action-btn" ng-click="$ctrl.cropCoverImage()" ng-disabled="$ctrl.cropUploading" aria-label="{{ \'Кадрировать обложку\' | t }}" data-tooltip="{{ \'Кадрировать\' | t }}" > <i class="fas fa-crop-alt" aria-hidden="true"></i> </button> <button type="button" class="cover-action-btn" ngf-select="$ctrl.uploadImage($file)" ngf-accept="\'image/*\'" ng-disabled="$ctrl.cropUploading || $ctrl.imageUploading[\'root\']" aria-label="{{ \'Заменить обложку\' | t }}" data-tooltip="{{ \'Заменить\' | t }}" > <i class="fas fa-image" aria-hidden="true"></i> </button> <button type="button" class="cover-action-btn cover-action-btn--delete" ng-click="$ctrl.deleteCoverImage()" ng-disabled="$ctrl.cropUploading" aria-label="{{ \'Удалить обложку\' | t }}" data-tooltip="{{ \'Удалить\' | t }}" > <i class="fas fa-times" aria-hidden="true"></i> </button> </div> <ng-include src="\'/pages/edit/editContentItem/partials/cover-actions-mobile.html\'" ></ng-include> <span class="fas fa-sync-alt fa-spin crop-upload-spinner" ng-show="$ctrl.cropUploading" ></span> <img class="tw:block tw:max-w-full tw:h-auto thumbmail" ng-class="{\'is-uploading\': $ctrl.cropUploading }" ng-src="{{ contentItem.thumborId | imageUrl: 300 }}" ng-click="$ctrl.cropCoverImage()" alt="{{ (\'Обложка:\' | t) + \' \' + (contentItem.title || (\'видео\' | t)) + \' \' + (\'— Дискурс\' | t) }}" /> </div> </div> </div> </div> <div class="tw:flex tw:flex-wrap" ng-hide="$ctrl.isPreviewMode"> <div class="video-link-col tw:sm:w-5/12"> <div class="form-group video-link-form"> <label>{{ \'Ссылка на видео\' | t }}</label> <input class="ds-input video-link-input" placeholder="{{ \'Укажите ссылку\' | t }}" ng-model="contentItem.media[0].videoUrl" /> </div> </div> <div class="tw:sm:w-2/12 tw:text-center tw:mt-section-xl" ng-hide="contentItem.media[0].videoUrl"> <p class="tw:font-bold">{{ \'ИЛИ\' | t }}</p> </div> <div class="video-upload-col tw:sm:w-5/12" ng-hide="contentItem.media[0].videoUrl"> <div class="form-group"> <button class="small-button thin" type="button" ng-class="{\'uploading-process\': $ctrl.videoUploading[0] }" ngf-select="$ctrl.uploadVideo($file, 0)" accept="video/*" required="required" ng-disabled="$ctrl.videoUploading[0]" > <span class="floating-text floating-top" ng-hide="$ctrl.videoUploading[0]" >{{ \'Загрузить видео\' | t }}</span ><span class="floating-text floating-top" ng-show="$ctrl.videoUploading[0]" >{{ \'Идет загрузка...\' | t }}</span ><span ng-hide="$ctrl.videoUploading[0]">{{ \'Выберите файл\' | t }}</span ><span class="fas fa-sync-alt fa-spin uploading-icon" ng-show="$ctrl.videoUploading[0]" ></span ><span class="floating-text floating-bottom">*MP4, AVI, MOV</span> </button> </div> </div> </div> <div class="form-group discours-editor big tiptap-mode" ng-show="!$ctrl.isPreviewMode" ng-class="{\'has-editor-header\': $ctrl.showEditorHeader }" >
1 <div class="tiptap-loading" ng-if="!$ctrl.tiptapReady"> <div class="loading-progress-bar"> <div class="loading-progress-fill"></div> </div> <div class="tiptap-loading-skeleton"> <div class="tiptap-loading-skeleton__line tiptap-loading-skeleton__line--60"></div> <div class="tiptap-loading-skeleton__gap"></div> <div class="tiptap-loading-skeleton__line tiptap-loading-skeleton__line--85"></div> <div class="tiptap-loading-skeleton__line tiptap-loading-skeleton__line--75"></div> </div> </div> <div ng-if="$ctrl.tiptapReady" class="tiptap-editor-container"> <div class="tiptap-editor-wrapper"> <ng-include ng-if="!$ctrl.commenterReadOnly" src="\'/pages/edit/editContentItem/partials/tiptap-toolbar.html\'" ></ng-include> <tiptap-editor ng-model="contentItem.body" placeholder="{{ \'Текст\' | t }}" editable="true" editor-instance="$ctrl.tiptapExpoEditor" content-item="contentItem" initial-content="contentItem.body" ></tiptap-editor> </div> <div class="tiptap-bottom-bar"> <div class="bottom-bar-left"></div> <div class="bottom-bar-right" ng-show="$ctrl.expoAutosaveStatus" aria-live="polite"> <span class="expo-autosave-indicator expo-autosave-indicator--{{ $ctrl.expoAutosaveStatus }}" > <i ng-if="$ctrl.expoAutosaveStatus === \'saving\'" class="fas fa-circle-notch fa-spin" aria-hidden="true" ></i> <i ng-if="$ctrl.expoAutosaveStatus === \'saved\'" class="fas fa-check" aria-hidden="true" ></i> <i ng-if="$ctrl.expoAutosaveStatus === \'error\'" class="fas fa-exclamation-triangle" aria-hidden="true" ></i> <i ng-if="$ctrl.expoAutosaveStatus === \'retrying\'" class="fas fa-sync fa-spin" aria-hidden="true" ></i> <span ng-if="$ctrl.expoAutosaveStatus === \'saving\'">{{ \'Сохранение...\' | t }}</span> <span ng-if="$ctrl.expoAutosaveStatus === \'saved\'">{{ \'Сохранено\' | t }}</span> <span ng-if="$ctrl.expoAutosaveStatus === \'error\'">{{ \'Ошибка сохранения\' | t }}</span> <span ng-if="$ctrl.expoAutosaveStatus === \'retrying\'">{{ \'Повтор...\' | t }}</span> </span> </div> </div> </div> </div>'),s.put("/pages/edit/editContentItem/types/VideoAlbum.html",'<ng-include src="\'/pages/edit/editContentItem/partials/expo-preview.html\'" ng-if="$ctrl.isPreviewMode" ></ng-include> <editor-header ng-if="$ctrl.showEditorHeader" doc-title="contentItem.title" slug="contentItem.slug" content-status="contentItem.status" content-link="contentItem.link" tiptap-editor="$ctrl.tiptapExpoEditor" is-preview-mode="$ctrl.isPreviewMode" is-saving="$ctrl.isSaving" is-publishing="$ctrl.isPublishing" is-save-error="$ctrl.isSaveError" has-unsaved-changes="$ctrl.hasUnsavedChanges" has-draft="$ctrl.hasDraft" can-edit="$ctrl.canEdit === true" on-mode-change="$ctrl.onEditorModeChange(mode)" on-save="contentItem.slug ? $ctrl.saveAndCreateVersion() : $ctrl.saveDraft()" on-save-version-dialog="$ctrl.openVersionNameDialog()" on-versions-click="$ctrl.toggleVersions()" is-saving-version="$ctrl.savingVersion" is-save-success="$ctrl.saveSuccess" last-version-saved-at="$ctrl.lastVersionSavedAt" on-publish="$ctrl.sendFormForPublish()" is-admin="$ctrl.isAdmin" on-open-settings="$ctrl.openPublishSettings()" on-run-typographer="$ctrl.applyTypography()" on-clean-code="$ctrl.cleanHtmlContent()" word-count="$ctrl.wordCount" char-count="$ctrl.charCount" initial-para-count="$ctrl.paraCount" is-logged-in="$ctrl.isLoggedIn" user-avatar="$ctrl.userAvatarId" user-path="$ctrl.userPathSlug" night-mode="$ctrl.darkMode" on-toggle-night-mode="$ctrl.toggleDarkMode()" ></editor-header> <div class="title-input-row tw:flex tw:flex-wrap" ng-hide="$ctrl.isPreviewMode"> <div class="title-col tw:sm:w-8/12 tw:lg:w-9/12"> <div class="form-group tw:font-bold title" ng-class="{\'has-error\': formErrors.title}"> <textarea class="ds-input big-textarea placeholder-big" placeholder="{{ \'Название альбома\' | t }}" ng-model="contentItem.title" ng-change="formErrors.title = false" maxlength="120" textarea-limit-rows="textarea-limit-rows" rows="1" msd-elastic aria-label="{{ \'Название видеоальбома\' | t }}" aria-invalid="{{ !!formErrors.title }}" aria-describedby="form-error-title" ></textarea> <span id="form-error-title" class="help-block" ng-show="formErrors.title" role="alert" >{{ \'Заголовок обязателен\' | t }}</span > </div> </div> <div class="cover-col tw:sm:w-4/12 tw:lg:w-3/12 tw:mb-2.5"> <div class="form-group"> <button class="big-button" type="button" ngf-select="$ctrl.uploadImage($file)" ngf-drop="$ctrl.uploadImage($file)" ngf-drag-over-class="\'drag-over\'" ngf-accept="\'image/*\'" ng-disabled="$ctrl.imageUploading[\'root\']" ng-hide="contentItem.thumborId" > <p class="fa fa-paperclip fa-4x"></p> <div>{{ \'Загрузить обложку\' | t }}<br />(jpeg, png, gif)</div> <span class="fas fa-sync-alt fa-spin floating-text left" ng-show="$ctrl.imageUploading[\'root\']" ></span> </button> <div class="post-image-wrapper" ng-if="contentItem.thumborId"> <div class="cover-actions cover-actions--desktop"> <button type="button" class="cover-action-btn" ng-click="$ctrl.cropCoverImage()" ng-disabled="$ctrl.cropUploading" aria-label="{{ \'Кадрировать обложку\' | t }}" data-tooltip="{{ \'Кадрировать\' | t }}" > <i class="fas fa-crop-alt" aria-hidden="true"></i> </button> <button type="button" class="cover-action-btn" ngf-select="$ctrl.uploadImage($file)" ngf-accept="\'image/*\'" ng-disabled="$ctrl.cropUploading || $ctrl.imageUploading[\'root\']" aria-label="{{ \'Заменить обложку\' | t }}" data-tooltip="{{ \'Заменить\' | t }}" > <i class="fas fa-image" aria-hidden="true"></i> </button> <button type="button" class="cover-action-btn cover-action-btn--delete" ng-click="$ctrl.deleteCoverImage()" ng-disabled="$ctrl.cropUploading" aria-label="{{ \'Удалить обложку\' | t }}" data-tooltip="{{ \'Удалить\' | t }}" > <i class="fas fa-times" aria-hidden="true"></i> </button> </div> <ng-include src="\'/pages/edit/editContentItem/partials/cover-actions-mobile.html\'" ></ng-include> <span class="fas fa-sync-alt fa-spin crop-upload-spinner" ng-show="$ctrl.cropUploading" ></span> <img class="tw:block tw:max-w-full tw:h-auto thumbmail" ng-class="{\'is-uploading\': $ctrl.cropUploading }" ng-src="{{ contentItem.thumborId | imageUrl: 300 }}" ng-click="$ctrl.cropCoverImage()" alt="{{ (\'Обложка:\' | t) + \' \' + (contentItem.title || (\'видеоальбом\' | t)) + \' — Дискурс\' }}" /> </div> </div> </div> </div> <div class="media-row tw:flex tw:flex-wrap tw:mt-section" ng-repeat="mediaItem in contentItem.media track by $index" > <div class="media-thumb-col tw:sm:w-4/12"> <div class="form-group tw:relative media-item-actions">
1 <button class="big-button thin small" type="button" ngf-select="$ctrl.uploadVideo($file, $index)" accept="video/*" ng-disabled="$ctrl.videoUploading[$index]" ng-hide="mediaItem.videoUrl" > <img src="/images/icons/attach.png" alt="{{ \'Загрузить\' | t }}" /><span >{{ \'загрузить видео\' | t }}</span ><span class="fas fa-sync-alt fa-spin floating-text left" ng-show="$ctrl.videoUploading[$index]" ></span ><span class="floating-text">*MP4,AVI,MOV</span> </button> <p class="tw:font-bold tw:mt-2.5 tw:mb-2.5" ng-hide="mediaItem.videoUrl">{{ \'ИЛИ\' | t }}</p> <div class="form-group"> <input class="ds-input" placeholder="{{ \'ссылка (Youtube, Vimeo)\' | t }}" ng-model="mediaItem.videoUrl" /> </div> </div> </div> <div class="media-content-col tw:sm:w-8/12"> <div class="form-group tw:font-bold" ng-class="{\'tw:mr-section\': $index != 0 }"> <textarea class="ds-input" placeholder="{{ \'название видео\' | t }}" ng-model="mediaItem.title" aria-label="{{ \'Название видео в альбоме\' | t }}" ></textarea> </div> <div class="form-group discours-editor xs small-margin tiptap-mode" ng-show="!$ctrl.isPreviewMode" ng-class="{\'has-editor-header\': $index === 0 && $ctrl.showEditorHeader }" > <tiptap-simple-editor ng-model="mediaItem.body" preset="simplified" placeholder="{{ \'Текст\' | t }}" ></tiptap-simple-editor> </div> </div> </div> <div ng-if="!$ctrl.isPreviewMode"> <div> <div class="tiptap-bottom-bar"> <div class="bottom-bar-left"></div> <div class="bottom-bar-right" ng-show="$ctrl.expoAutosaveStatus" aria-live="polite"> <span class="expo-autosave-indicator expo-autosave-indicator--{{ $ctrl.expoAutosaveStatus }}" > <i ng-if="$ctrl.expoAutosaveStatus === \'saving\'" class="fas fa-circle-notch fa-spin" aria-hidden="true" ></i> <i ng-if="$ctrl.expoAutosaveStatus === \'saved\'" class="fas fa-check" aria-hidden="true" ></i> <i ng-if="$ctrl.expoAutosaveStatus === \'error\'" class="fas fa-exclamation-triangle" aria-hidden="true" ></i> <i ng-if="$ctrl.expoAutosaveStatus === \'retrying\'" class="fas fa-sync fa-spin" aria-hidden="true" ></i> <span ng-if="$ctrl.expoAutosaveStatus === \'saving\'">{{ \'Сохранение...\' | t }}</span> <span ng-if="$ctrl.expoAutosaveStatus === \'saved\'">{{ \'Сохранено\' | t }}</span> <span ng-if="$ctrl.expoAutosaveStatus === \'error\'">{{ \'Ошибка сохранения\' | t }}</span> <span ng-if="$ctrl.expoAutosaveStatus === \'retrying\'">{{ \'Повтор...\' | t }}</span> </span> </div> </div> </div> </div> <div class="tw:mb-section-lg"> <div class="discours-form"> <button class="ds-btn-border add-image-button" ng-click="contentItem.media.push({})" type="button" > <i class="fas fa-plus fa-3x"></i>{{ \'добавить видео\' | t }} </button> </div> </div>'),s.put("/pages/editor-poc/editor-poc.html",'<link rel="stylesheet" href="/styles/editor-e839e09a.css" /> <style> .editor-poc-page { opacity: 0; transition: opacity 0.15s; } </style> <link rel="stylesheet" href="/styles/editor-poc-38826fe3.css" id="editor-poc-css" /> <script> (function () { var link = document.getElementById(\'editor-poc-css\'); var reveal = function () { var el = document.querySelector(\'.editor-poc-page\'); // Именно \'1\', а не \'\': сброс инлайнового стиля возвращает элемент под // правило `.editor-poc-page { opacity: 0 }` из <style> выше — той же // специфичности и без конкурента, потому что editor-poc.css своего // `opacity` для этого класса не задаёт. Со сбросом страница оставалась // невидимой ВСЕГДА (замер 22.08.2026 на deploy-preview: css 200, // link.sheet есть, reveal сработал, inline style пуст, // getComputedStyle().opacity === \'0\' при 154 отрендеренных узлах). // Ратчет — scripts/editor-poc-reveal.test.js. if (el) el.style.opacity = \'1\'; }; if (link) { link.addEventListener(\'load\', reveal); link.addEventListener(\'error\', reveal); // Fallback: если CSS уже загружен из кэша if (link.sheet) reveal(); } // Fallback: reveal page after 3s even if CSS fails to load (network error, 404, etc.) setTimeout(reveal, 3000); })(); <\/script> <div class="editor-poc-page" ng-class="{\'preview-mode\': previewMode, \'has-editor-header\': editorInstance }" > <editor-header ng-if="editorInstance" doc-title="documentTitle || \'Новый материал\'" collab-status="collabStatus" connected-users="[]" is-offline-mode="collabStatus === \'offline\'" is-reconnecting="collabStatus === \'connecting\'" tiptap-editor="editorInstance" suggestion-mode="suggestionMode" sidebar-visible="showCommentsSidebar" pending-count="comments.length + suggestions.all.length" is-saving="isSaving" is-publishing="isPublishing" has-unsaved-changes="hasUnsavedChanges" has-draft="hasDraft" can-edit="true" show-collab-control
1s="collabEnabled" show-invite-button="collabEnabled" on-open-invite="openInviteModal()" on-mode-change="onEditorModeChange(mode)" on-toggle-sidebar="toggleSidebar()" on-publish="publishDocument()" on-retry-connection="retryConnection()" ></editor-header> <invite-collaborators opened="inviteModalOpen" document-name="collabDocumentName" on-invites-changed="onInvitesChanged(invites)" ></invite-collaborators> <div class="editor-poc-header" ng-hide="editorInstance"> <h1>Tiptap Editor PoC</h1> <p class="editor-poc-subtitle"> Proof of Concept коллаборативного редактора с комментариями и предложениями правок </p> <div class="collab-status-panel" ng-class="collabStatus"> <div class="collab-info" ng-if="collabEnabled"> <span class="collab-indicator"></span> <span class="collab-status-text"> <span ng-if="collabStatus === \'connecting\'">Подключение к серверу...</span> <span ng-if="collabStatus === \'connected\'">Подключено</span> <span ng-if="collabStatus === \'synced\'">Синхронизировано</span> <span ng-if="collabStatus === \'disconnected\'">Отключено (переподключение...)</span> <span ng-if="collabStatus === \'offline\'">Автономный режим</span> <span ng-if="collabStatus === \'auth-failed\'">Ошибка авторизации</span> </span> <span class="collab-server">{{ collabServerUrl }}</span> <span class="collab-doc">Документ: {{ collabDocumentName }}</span> </div> <div class="collab-info collab-disabled" ng-if="!collabEnabled"> <span class="collab-indicator"></span> <span class="collab-status-text">Коллаборация отключена</span> <span class="collab-hint" ng-if="!currentUser._id || currentUser._id === \'anonymous\'"> (войдите в аккаунт для совместного редактирования) </span> </div> <div class="save-status-info"> <span class="save-indicator" ng-class="{\'saving\': isSaving, \'unsaved\': hasUnsavedChanges && !isSaving, \'error\': saveError}" > <span ng-if="isSaving">Сохранение...</span> <span ng-if="!isSaving && saveError === \'conflict\'" >⚠ Конфликт версий - обновите страницу</span > <span ng-if="!isSaving && saveError === \'error\'">⚠ Ошибка сохранения</span> <span ng-if="!isSaving && !saveError && hasUnsavedChanges" >● Есть несохранённые изменения</span > <span ng-if="!isSaving && !saveError && !hasUnsavedChanges && lastSavedAt" >Сохранено {{ lastSavedAt | date:\'HH:mm:ss\' }}</span > </span> </div> </div> <div class="draft-recovery-banner" ng-if="hasRecoverableDraft"> <span class="draft-recovery-text"> Обнаружен несохранённый черновик от {{ draftBackup.timestamp | date:\'dd.MM.yyyy HH:mm\' }} </span> <button type="button" class="draft-recovery-btn restore" ng-click="restoreDraftBackup()"> Восстановить черновик </button> <button type="button" class="draft-recovery-btn dismiss" ng-click="dismissDraftBackup()"> Отклонить </button> </div> <div class="offline-banner" ng-if="isOffline"> <span class="offline-icon">⚠</span> <span class="offline-text" >Нет подключения к сети. Редактирование продолжается локально. Изменения будут синхронизированы при восстановлении связи.</span > </div> </div> <div class="editor-poc-toolbar" ng-hide="previewMode"> <div class="toolbar-group"> <button type="button" class="toolbar-btn" ng-class="{ active: isActive(\'bold\') }" ng-click="execCommand(\'bold\')" title="Жирный (Ctrl+B)" > <strong>B</strong> </button> <button type="button" class="toolbar-btn" ng-class="{ active: isActive(\'italic\') }" ng-click="execCommand(\'italic\')" title="Курсив (Ctrl+I)" > <em>I</em> </button> <button type="button" class="toolbar-btn" ng-class="{ active: isActive(\'underline\') }" ng-click="execCommand(\'underline\')" title="Подчёркивание (Ctrl+U)" > <u>U</u> </button> <button type="button" class="toolbar-btn" ng-class="{ active: isActive(\'strike\') }" ng-click="execCommand(\'strike\')" title="Зачёркнутый" > <s>S</s> </button> </div> <div class="toolbar-separator"></div> <div class="toolbar-group"> <button type="button" class="toolbar-btn" ng-class="{ active: isActive(\'h2\') }" ng-click="execCommand(\'h2\')" title="Заголовок H2" > H2 </button> <button type="button" class="toolbar-btn" ng-class="{ active: isActive(\'h3\') }" ng-click="execCommand(\'h3\')" title="Заголовок H3" > H3 </button> </div> <div class="toolbar-separator"></div> <div class="toolbar-group"> <button type="button" class="toolbar-btn" ng-class="{ active: isActive(\'bulletList\') }" ng-click="execCommand(\'bulletList\')" title="Маркированный список" > <i class="fas fa-list-ul"></i> </button> <button type="button" class="toolbar-btn" ng-class="{ active: isActive(\'orderedList\') }" ng-click="execCommand(\'orderedList\')" title="Нумерованный список" > <i class="fas fa-list-ol"></i> </button> </div> <div class="toolbar-separator"></div> <div class="toolbar-group"> <button type="button" class="toolbar-btn" ng-class="{ active: isActive(\'blockquote\') }" ng-click="execCommand(\'blockquote\')" title="Цитата" > <i class="fas fa-quote-left"></i> </button> <button type="button" class="toolbar-btn toolbar-btn-tasub" ng-class="{ active: isActive(\'taSub\') }" ng-click="execCommand(\'taSub\')" title="Подвёрстка (серая плашка)" > SUB </button> </div> <div class="toolbar-separator"></div> <div class="toolbar-group"> <button type="button" class="toolbar-btn" ng-class="{ active: isActive(\'link\') }" ng-click="execCommand(\'link\')" title="Вставить ссылку" > <i class="fas fa-link"></i> </button> <button type="button" class="toolbar-btn" ng-click="execCommand(\'unlink\')" ng-disabled="!isActive(\'link\')" title="Убрать ссылку" > <i class="fas fa-unlink"></i> </button> </div> <div class="toolbar-separator"></div> <div class="toolbar-group"> <button type="button" class="toolbar-btn" ng-click="execCommand(\'undo\')" title="Отменить (Ctrl+Z)" > <i class="fas fa-undo"></i> </button> <button type="button" class="toolbar-btn" ng-click="execCommand(\'redo\')" title="Повторить (Ctrl+Shift+Z)" > <i class="fas fa-redo"></i> </button> </div> <div class="toolbar-spacer"></div> <div class="toolbar-group toolbar-group-collab"> <button type="button" class="toolbar-btn toolbar-btn-comment" ng-click="addComment()" ng-disabled="!canAddComment()" title="Добавить комментарий к выделенному тексту" > <i class="fas fa-comment"></i> Комментарий </button> <button type="button" class="toolbar-btn toolbar-btn-suggestion" ng-class="{ active: suggestionMode }" ng-click="toggleSuggestionMode()" title="Режим предложений (track changes)" > <i class="fas fa-pencil-alt"></i> {{ suggestionMode ? \'Выйти из режима\' : \'Предложить правки\' }} </button> <button type="button" class="toolbar-btn" ng-click="toggleSidebar()" title="Показать/скрыть панель комментариев" > <i class="fas fa-columns"></i> </button> </div> </div> <div class="post-preview" ng-if="previewMode && previewPost"> <div class="ds-container"> <div class="button-container"> <button type="button" class="preview-close-button" ng-click="togglePreviewMode()"> <svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg" > <path d="M17.9975 4.89363L13.1038 0L0 13.1064V18H4.89363L17.9975 4.89363ZM13.105 2.55674L15.4421 4.89383L14.4612 5.87591L12.1241 3.53882L13.105 2.55674ZM10.8453 4.81648L13.1824 7.
115357L5.42174 14.9137L3.08485 12.5756L10.8453 4.81648ZM1.80725 16.1915V13.8544L4.14434 16.1915H1.80725Z" fill="currentColor" /> </svg> Продолжить редактировать </button> </div> </div> <div class="ds-container"> <div class="open-post progress"> <div class="tw:row-flex"> <div class="ds-content-col"> <div class="post-main-spacer tw:mt-section tw:max-md:hidden"></div> <div class="author-category" ng-class="{\'no-author\': !previewPost.author }"> <span class="category" ng-if="previewPost.category"> <a ng-href="{{ previewPost.categoryLink }}" rel="noopener noreferrer" target="_blank" >{{ previewPost.category }}</a > </span> <span class="author" ng-if="previewPost.author"> <a ng-href="{{ previewPost.authorLink }}" rel="noopener noreferrer" target="_blank" ng-if="previewPost.authorLink" >{{ previewPost.author }}</a > <span ng-if="!previewPost.authorLink">{{ previewPost.author }}</span> </span> </div> <h1 class="title">{{ previewPost.title }}</h1> <div class="open-post-title-image-wrapper" ng-if="previewPost.thumborId"> <img class="open-post-title-image" ng-src="{{ previewPost.thumborId | imageUrl: 750 }}" alt="{{ previewPost.title }}" /> </div> <p class="image-description" ng-if="previewPost.imageCaption" ng-bind-html="previewPost.imageCaption | trustedHtml" ></p> </div> </div> <div class="tw:row-flex"> <div class="ds-content-col"> <div class="post-body" ng-bind-html="previewPost.body | trustedHtml" post-links="post-links" post-images="post-images" ></div> </div> </div> <div class="footer-post tw:row-flex"> <div class="ds-content-col"> <hr class="tw:mt-2.5 tw:mb-5" /> <div class="post-meta-footer"> <div class="post-meta-footer__author"> <div class="author-title" ng-if="previewPost.author"> <a ng-href="{{ previewPost.authorLink }}" rel="noopener noreferrer" target="_blank" ng-if="previewPost.authorLink" >{{ previewPost.author }}</a > <span ng-if="!previewPost.authorLink">{{ previewPost.author }}</span> </div> <div class="date-time"> <span class="time" >{{ previewPost.publishedAt | amDateFormat: \'D MMMM YYYY\' }}</span > </div> </div> </div> <div class="tw:mt-2.5" ng-if="previewPost.tags.length"> <div class="tags-container"> <ul class="tags"> <li ng-repeat="tag in previewPost.tags track by tag.slug"> <a ng-href="/tags/{{ tag.slug }}" rel="noopener noreferrer" target="_blank" >#{{ tag.title }}</a > </li> </ul> </div> </div> </div> </div> </div> </div> <div class="ds-container" ng-if="showBottomEditButton"> <div class="button-container"> <button type="button" class="preview-close-button bottom" ng-click="togglePreviewMode()"> <svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg" > <path d="M17.9975 4.89363L13.1038 0L0 13.1064V18H4.89363L17.9975 4.89363ZM13.105 2.55674L15.4421 4.89383L14.4612 5.87591L12.1241 3.53882L13.105 2.55674ZM10.8453 4.81648L13.1824 7.15357L5.42174 14.9137L3.08485 12.5756L10.8453 4.81648ZM1.80725 16.1915V13.8544L4.14434 16.1915H1.80725Z" fill="currentColor" /> </svg> Продолжить редактировать </button> </div> </div> </div> <div class="editor-poc-container" ng-class="{\'with-sidebar\': showCommentsSidebar }" ng-if="!previewMode" > <div class="toc-nav-container" id="toc-nav-container"></div> <div class="editor-poc-main"> <div class="editor-poc-editor" ng-class="{\'suggestion-mode\': suggestionMode }"> <tiptap-editor ng-model="content" placeholder="Начните писать..." user-id="userId" user-name="currentUser.name" editor-instance="editorInstance" on-selection-change="onSelectionChange(selection)" collab-server-url="{{ collabServerUrl }}" collab-document-name="collabDocumentName" collab-token="collabToken" collab-status="collabStatus" on-collab-synced="onCollabSynced()" on-collab-disconnect="onCollabDisconnect()" on-collab-connect="onCollabConnect()" on-collab-fallback="onCollabFallback(reason)" initial-content="initialContent" ></tiptap-editor> </div> <div class="suggestion-mode-indicator" ng-if="suggestionMode"> <i class="fas fa-pencil-alt"></i> Режим предложений активен. Ваши правки будут отмечены для проверки. </div> <div class="editor-poc-footer"> <div class="footer-spacer"></div> <button type="button" class="preview-toggle-btn" ng-click="togglePreviewMode()" title="Предпросмотр" > <svg width="21" height="14" viewBox="0 0 21 14" fill="none" xmlns="http://www.w3.org/2000/svg" > <path d="M10.125 0C4.6875 0 0 4.52865 0 6.75C0 8.97135 4.6875 13.5 10.125 13.5C15.5625 13.5 20.25 8.97135 20.25 6.75C20.25 4.52865 15.5625 0 10.125 0ZM10.125 1.5C14.9792 1.5 18.75 5.687
15 18.75 6.75C18.75 7.8125 14.9792 12 10.125 12C5.27083 12 1.5 7.8125 1.5 6.75C1.5 5.6875 5.27083 1.5 10.125 1.5ZM10.125 2.625C7.84635 2.625 6 4.47135 6 6.75C6 9.02865 7.84635 10.875 10.125 10.875C12.4036 10.875 14.25 9.02865 14.25 6.75C14.25 4.47135 12.4036 2.625 10.125 2.625ZM10.125 5.25C10.9531 5.25 11.625 5.92188 11.625 6.75C11.625 7.57813 10.9531 8.25 10.125 8.25C9.29688 8.25 8.625 7.57813 8.625 6.75C8.625 5.92188 9.29688 5.25 10.125 5.25Z" fill="currentColor" /> </svg> Предпросмотр </button> </div> </div> <div class="editor-poc-sidebar" ng-show="showCommentsSidebar"> <div class="sidebar-tabs"> <button type="button" class="sidebar-tab" ng-class="{ active: sidebarTab !==\'suggestions\' }" ng-click="sidebarTab = \'comments\'" > Комментарии ({{ comments.length }}) </button> <button type="button" class="sidebar-tab" ng-class="{ active: sidebarTab === \'suggestions\' }" ng-click="sidebarTab = \'suggestions\'" > Правки ({{ suggestions.all.length }}) </button> </div> <div class="collab-capability-notice" ng-if="collabCommentStoreUnavailable" role="status" aria-live="polite" > {{ collabCommentStoreMessage | t }} </div> <div class="collab-capability-notice collab-recovery-list" ng-if="collabCommentRecoveryDrafts.length" > <p role="status" aria-live="polite" aria-atomic="true"> {{ \'Неотправленный текст сохранён ниже. Скопируйте его перед выходом со страницы\' | t }} </p> <div class="collab-recovery-draft" ng-repeat="draft in collabCommentRecoveryDrafts track by draft.id" > <label for="poc-collab-recovery-{{ ::$index }}">{{ draft.label | t }}</label> <textarea id="poc-collab-recovery-{{ ::$index }}" readonly rows="3" ng-model="draft.text" ></textarea> </div> </div> <div class="sidebar-content" ng-if="sidebarTab !== \'suggestions\'"> <div class="comments-list" ng-if="comments.length > 0"> <div class="comment-item" ng-repeat="comment in comments track by comment.id" ng-class="{ resolved: comment.resolved, editing: comment.editing, active: activeCommentId === comment.id }" ng-click="scrollToComment(comment)" > <div class="comment-header"> <span class="comment-author">{{ comment.author.name || \'Автор\' }}</span> <span class="comment-date">{{ comment.createdAt | date:\'dd.MM.yyyy HH:mm\' }}</span> <span class="comment-resolved-badge" ng-if="comment.resolved">Решено</span> </div> <div class="comment-body" ng-if="!comment.editing">{{ comment.text }}</div> <div class="comment-edit" ng-if="comment.editing"> <textarea ng-model="comment.text" placeholder="Введите комментарий..." rows="3" ng-readonly="collabCommentStoreUnavailable" ></textarea> <div class="comment-actions"> <button type="button" class="btn-save" ng-click="saveComment(comment)" ng-disabled="collabCommentStoreUnavailable" > Отправить </button> <button type="button" class="btn-cancel" ng-click="cancelComment(comment)"> Отмена </button> </div> </div> <div class="comment-footer" ng-if="!comment.editing"> <button type="button" class="ds-btn-link" ng-click="editComment(comment)" ng-disabled="collabCommentStoreUnavailable" > Изменить </button> <button type="button" class="ds-btn-link" ng-click="resolveComment(comment)" ng-if="!comment.resolved" ng-disabled="collabCommentStoreUnavailable" > Решено </button> <button type="button" class="ds-btn-link" ng-click="unresolveComment(comment)" ng-if="comment.resolved" ng-disabled="collabCommentStoreUnavailable" > Открыть снова </button> <button type="button" class="ds-btn-link ds-btn-danger" ng-click="removeComment(comment)" ng-disabled="collabCommentStoreUnavailable" > Удалить </button> </div> <div class="comment-replies" ng-if="comment.replies.length > 0"> <div class="reply-item" ng-repeat="reply in comment.replies track by (reply._id || $index)" > <span class="reply-author">{{ reply.author.name }}</span>: {{ reply.text }} </div> </div> <div class="comment-add-reply" ng-if="!comment.editing && !comment.resolved"> <input type="text" ng-model="comment.newReplyText" placeholder="Ответить..." ng-keypress="$event.keyCode === 13 && addReply(comment, comment.
1newReplyText)" ng-readonly="collabCommentStoreUnavailable" /> </div> </div> </div> <div class="sidebar-empty" ng-if="comments.length === 0 && !collabCommentStoreUnavailable"> <i class="fas fa-comment"></i> <p>Нет комментариев</p> <p class="hint">Выделите текст и нажмите "Комментарий"</p> </div> </div> <div class="sidebar-content" ng-if="sidebarTab === \'suggestions\'"> <div class="suggestions-actions" ng-if="suggestions.all.length > 0"> <button type="button" class="btn-accept-all" ng-click="acceptAllSuggestions()"> <i class="fas fa-check-double"></i> Принять все </button> <button type="button" class="btn-reject-all" ng-click="rejectAllSuggestions()"> <i class="fas fa-times"></i> Отклонить все </button> </div> <div class="suggestions-list" ng-if="suggestions.all.length > 0"> <div class="suggestion-item" ng-repeat="suggestion in suggestions.all track by suggestion.suggestionId" ng-class="{\'suggestion-add\': suggestion.type === \'add\', \'suggestion-delete\': suggestion.type === \'delete\' }" > <div class="suggestion-header"> <span class="suggestion-type" ng-if="suggestion.type === \'add\'"> <i class="fas fa-plus"></i> Добавление </span> <span class="suggestion-type" ng-if="suggestion.type === \'delete\'"> <i class="fas fa-minus"></i> Удаление </span> </div> <div class="suggestion-text">"{{ suggestion.text | limitTo:100 }}"</div> <div class="suggestion-actions"> <button type="button" class="btn-accept" ng-click="acceptSuggestion(suggestion); $event.stopPropagation()" > <i class="fas fa-check"></i> Принять </button> <button type="button" class="btn-reject" ng-click="rejectSuggestion(suggestion); $event.stopPropagation()" > <i class="fas fa-times"></i> Отклонить </button> </div> </div> </div> <div class="sidebar-empty" ng-if="suggestions.all.length === 0"> <i class="fas fa-pencil-alt"></i> <p>Нет предложений</p> <p class="hint">Включите "Режим предложений" и редактируйте текст</p> </div> </div> </div> </div> <div class="editor-poc-debug" ng-if="DISCOURS_ENV !== \'production\'"> <h4>Отладка</h4> <div class="debug-actions"> <button type="button" ng-click="loadSampleContent()">Загрузить пример</button> <button type="button" ng-click="clearContent()">Очистить</button> <button type="button" ng-click="getHTML()">Вывести HTML в консоль</button> </div> <div class="debug-info"> <p> <strong>Выделение:</strong> {{ selection.hasSelection ? selection.selectedText : \'нет\' }} </p> <p><strong>Режим предложений:</strong> {{ suggestionMode ? \'Да\' : \'Нет\' }}</p> <p><strong>Комментариев:</strong> {{ comments.length }}</p> <p><strong>Предложений:</strong> {{ suggestions.all.length }}</p> </div> </div> </div>')}catch(t){}}(0)},53320(t,e,n){"use strict";var i=n(36817),s=n(74774),a=n(31455),o=n(88484);const l=(...t)=>window.TIPTAP_DEBUG&&void 0;angular.module("discours.directives").directive("tiptapEditor",["$timeout","$interval","apiClient","Notification",function(t,e,r,c){function d(t){if(!t)return!0;const e=t.replace(/<p>\s*<\/p>/g,"").replace(/<br\s*\/?>/gi,"").replace(/&nbsp;/gi,"").replace(/\s+/g,"").trim();return""===e||"<p></p>"===e}function g(t,e={}){const n={type:"hocuspocus_error",event:t,timestamp:(new Date).toISOString(),...e};if(window.__sentry){var i="websocket_closed"===t&&(null==e.code||-1!==u.indexOf(e.code)||e.hadSynced),s=-1!==m.indexOf(t);i||s||window.__sentry.captureException(new Error("HocusPocus: "+t),{extra:n,level:"warning",fingerprint:["hocuspocus-error",String(t||"unknown")]})}try{const t="tiptap_collab_errors",e=JSON.parse(localStorage.getItem(t)||"[]");e.push(n),e.length>10&&e.shift(),localStorage.setItem(t,JSON.stringify(e))}catch(t){}}const p=8e3;var u=[1e3,1001,1005,1006,1012,1013],m=["sync_timeout","early_disconnect","reconnection_timeout"];return{restrict:"E",require:"?ngModel",scope:{placeholder:"@",editable:"=?",userId:"=?",userName:"=?",userSlug:"=?",userAvatar:"=?",userColor:"=?",onSelectionChange:"&?",onFocus:"&?",onBlur:"&?",editorInstance:"=?",contentItem:"=?",collabServerUrl:"@?",collabDocumentName:"=?",collabToken:"=?",onCollabSynced:"&?",onCollabDisconnect:"&?",onCollabConnect:"&?",collabStatus:"=?",collabPermissionDenied:"=?",initialContent:"=?",onCollabFallback:"&?",onInitialContentReady:"&?",isOfflineMode:"=?",isReconnecting:"=?",lastSyncTime:"=?",connectionLatency:"=?",unsyncedCh
1angesCount:"=?"},template:'<div class="tiptap-editor-inner"><div class="tiptap-editor-loading-bar" ng-if="isLoading && !isReconnecting"><div class="loading-progress-fill"></div></div><div class="tiptap-editor-error" ng-if="loadError"><div class="error-message">{{loadError}}</div><button type="button" ng-click="retryInit()">Повторить загрузку</button></div><div class="tiptap-collab-reconnecting-banner" ng-if="isReconnecting"><span>Переподключение к серверу...</span></div><div class="tiptap-prosemirror-container" ng-hide="loadError" ng-class="{\'is-loading\': isLoading}"></div></div>',_debugDomObserver:null,link:function(u,m,v,b){function f(t){if(!k)return;const e=k.isSuggestionMode&&k.isSuggestionMode();e&&k.setSuggestionMode&&k.setSuggestionMode(!1);try{k.commands.setContent(t,!1),k.clearUndoHistory?k.clearUndoHistory():k.commands.clearHistory&&k.commands.clearHistory()}finally{e&&k.setSuggestionMode&&k.setSuggestionMode(!0)}}function h(){u.onInitialContentReady&&!S&&t(function(){S||u.onInitialContentReady()})}function w(e){z.cancelPending(),V&&(t.cancel(V),V=null),H&&(t.cancel(H),H=null);let n=null;if(k&&!k.isDestroyed)try{n=k.getHTML(),n&&!d(n)&&(u.initialContent=n,A=!1,l(n.substring(0,100)))}catch(t){}if(u.isOfflineMode=!0,u.isReconnecting=!1,u.collabStatus="offline",l(),k&&u.initialContent){const t=k.getHTML(),e=d(t);if(l(t?.substring(0,100)),e){l(),C=!0;try{f(x?.cleanHtml?x.cleanHtml(u.initialContent):(0,i.NT)(u.initialContent))}catch(t){}C=!1}}k&&!k.isDestroyed&&k.setEditable(!0),u.onCollabFallback&&t(()=>{u.onCollabFallback({reason:e}),t(()=>{if(!S&&k&&!k.isDestroyed&&k.isEditable)try{k.commands.focus()}catch(t){}},50)})}function y(){Promise.all([n.e(272),n.e(929),n.e(411),n.e(753),n.e(68),n.e(1)]).then(n.bind(n,36090)).then(function(e){x=e,u.isLoading=!1,u.loadError=null;(u.collabServerUrl?void 0!==v.collabServerUrl:"")&&!u.collabDocumentName?(l(),L=u.$watchGroup(["collabDocumentName","initialContent"],function(e){if(S)return void(L&&(L(),L=null));const[n,i]=e;if(n&&u.collabToken){if(!i&&!u._waitedForContent){u._waitedForContent=!0,l();const e=t(()=>{S||I||(l(),L&&(L(),L=null),_())},1500);return void F.push(e)}l(),L&&(L(),L=null),_()}})):S||(l(),_()),u.$applyAsync()}).catch(function(t){u.isLoading=!1,u.loadError="Не удалось загрузить редактор, проверьте соединение и попробуйте снова",c.error("Ошибка загрузки редактора"),u.$applyAsync()})}async function _(){if(S||I)return void l();I=!0;const{createTiptapEditor:n,extractComments:a,extractSuggestions:v,generateId:y,cleanHtml:_,isContentLikelyDuplicated:L}=x;try{let x=null,I=u.collabServerUrl&&u.collabDocumentName&&u.collabToken;l(),I?(u.collabStatus="connecting",V=t(()=>{S||"connecting"===u.collabStatus&&w("connection_timeout")},p),F.push(V),x={serverUrl:u.collabServerUrl,documentName:u.collabDocumentName,token:u.collabToken,onSynced:()=>
1{S||(z.reset(),D=!1,V&&(t.cancel(V),V=null),H&&(t.cancel(H),H=null),t(()=>{if(S||!k)return;u.collabStatus="synced",u.isOfflineMode=!1,u.isReconnecting=!1,T=!0,u.lastSyncTime=Date.now(),u.unsyncedChangesCount=0;const e=()=>{if(A||S||!k)return;const t=d(k.getHTML());if(!u.initialContent&&t)try{var e=localStorage.getItem("ls.contentItem.body");if(e){var n=JSON.parse(e);n&&"<p></p>"!==n&&(u.initialContent=n,localStorage.removeItem("ls.contentItem.body"))}}catch(t){}const a=!!u.initialContent;if(!t&&a&&/<iframe\s/i.test(u.initialContent)&&function(t,e){if(!t||!e)return!1;try{const n=(new DOMParser).parseFromString(e,"text/html").querySelectorAll("iframe[src]");if(0===n.length)return!1;const i=new Map;if(n.forEach(t=>{const e=t.getAttribute("src")||"";if(!e)return;const n=t.getAttribute("height"),s=t.style&&t.style.height,a=t.style&&t.style.minHeight;let o=null;n&&parseInt(n,10)>100?o=parseInt(n,10)+"px":a?o=a:s&&(o=s);const l=t.closest(".social-embed-container"),r=l?l.dataset.aspectRatio||l.style.paddingBottom:null;(o||r)&&i.set(e,{minHeight:o,aspectRatio:r})}),0===i.size)return!1;const{tr:s}=t.state;let a=!1;return t.state.doc.descendants((t,e)=>{if("socialEmbed"!==t.type.name)return;const n=t.attrs.minHeight,o=n?parseInt(n,10):0;let l=!1;if(n?1e3!==o||"piktochart"!==t.attrs.provider&&"auto"!==t.attrs.aspectRatio||(l=!0):("custom"===t.attrs.provider||!t.attrs.aspectRatio||"auto"===t.attrs.aspectRatio)&&(l=!0),l){const n=i.get(t.attrs.src||"");n&&n.minHeight&&(s.setNodeMarkup(e,void 0,{...t.attrs,minHeight:n.minHeight,aspectRatio:n.aspectRatio||t.attrs.aspectRatio}),a=!0)}}),!!a&&(t.view.dispatch(s),l(),!0)}catch(t){return!1}}(k,u.initialContent),t&&a){const t=k.getYDoc&&k.getYDoc();if(t){const e=t.getMap("documentInit"),n=String(t.clientID);return e.has("initBy")||e.set("initBy",n),void(U=setTimeout(function(){if(U=null,A||S||!k)return;if(!d(k.getHTML()))return A=!0,u.initialContent=null,void h();const t=e.get("initBy");if(t!==n&&t){l(),A=!0;let t=0;R=setInterval(function(){if(t++,S||!k||t>50)if(clearInterval(R),R=null,k&&!S&&u.initialContent&&d(k.getHTML())){let t=!1;try{t=e&&!e.has("initBy")}catch(t){}if(t)return l(),void(N=setTimeout(function(){if(N=null,!S&&k){if(d(k.getHTML())&&u.initialContent){l(),C=!0;try{f(_?_(u.initialContent):(0,i.NT)(u.initialContent))}catch(t){A=!1}C=!1,u.initialContent=null}h()}},2e3));l(),C=!0;try{f(_?_(u.initialContent):(0,i.NT)(u.initialContent))}catch(t){A=!1}C=!1,u.initialContent=null,h()}else k&&!S&&h();else d(k.getHTML())||(clearInterval(R),R=null,u.initialContent=null,h())},200)}else{if(!u.initialContent)return l(),A=!0,void h();A=!0,l(),C=!0;try{f(_?_(u.initialContent):(0,i.NT)(u.initialContent))}catch(t){A=!1}C=!1;const t=u.initialContent;u.initialContent=null;try{e.delete("initBy")}catch(t){}h();const n=k.getHTML();O=setTimeout(function(){if(k&&!S){try{const e=k.getHTML();if(e!==n)return void l();if(L&&L(e)&&t){C=!0;try{f(_?_(t):(0,i.NT)(t))}catch(t){}C=!1}}catch(t){}O=null}},3e3)}},s.S))}A=!0,l(),C=!0;try{f(_?_(u.initialContent):(0,i.NT)(u.initialContent))}catch(t){A=!1}C=!1,u.initialContent=null,h()}else t||(A=!0)};if(k){const n=k.getYDoc&&k.getYDoc();n?(n._errorHandlerAttached||(n._errorHandlerAttached=!0,n.on("error",e=>{u.isOfflineMode||t(()=>{w("ydoc_error"),c.error("Ошибка синхронизации. Работаем в автономном режиме.")})})),n.once("afterAllTransactions",()=>{requestAnimationFrame(()=>{t(e,0)})}),n.transact(()=>{})):requestAnimationFrame(()=>{t(e,100)})}u.onCollabSynced&&!S&&u.onCollabSynced()}))},onDisconnect:()=>{S||t(()=>{if(S)return;const t="connecting"===u.collabStatus||"connected"===u.collabStatus;if(u.collabStatus="disconnected",!T&&t&&!D)return g("early_disconnect",{documentName:u.collabDocumentName,reason:"disconnected_before_sync",hadSynced:!1}),void w("early_disconnect");u.onCollabDisconnect&&u.onCollabDisconnect()})},onConnect:()=>{S||(V&&(t.cancel(V),V=null),H&&t.cancel(H),H=t(()=>{S||"connected"===u.collabStatus&&(g("sync_timeout",{documentName:u.collabDocumentName,reason:"sync_not_completed_in_time",hadSynced:!1}),w("sync_timeout"))},12e3),F.push(H),t(()=>{S||(u.collabStatus="connected",u.onCollabConnect&&u.onCollabConnect())}
1))},onStatus:e=>{S||t(()=>{S||(u.collabStatus=e.status)})},onAuthenticationFailed:e=>{if(S)return;const n=e&&e.reason||"",i="API_UNAVAILABLE"===n;if("permission-denied"===n||"PERMISSION_DENIED"===n||"INVALID_DOCUMENT_NAME"===n||"NOT_FOUND"===n)t(()=>{S||(u.collabPermissionDenied=!0,u.collabPermissionReason=n,w("permission_denied"))});else{if(i){D=!0,H&&(t.cancel(H),H=null);const n=z.failed();return void g("auth_failed_transient",{documentName:u.collabDocumentName,reason:e.reason,attempt:n,hadSynced:T})}D=!1,g("auth_failed",{documentName:u.collabDocumentName,reason:e&&e.reason||"authentication_failed",hadSynced:T}),t(()=>{w("auth_failed")})}},onClose:e=>{S||(g("websocket_closed",{documentName:u.collabDocumentName,code:e?.code,reason:e?.reason,hadSynced:T}),T||u.isOfflineMode||D||t(()=>{w("connection_closed")}))}}):l();var B=b&&b.$viewValue||u.initialContent||"";const q=B?_?_(B):(0,i.NT)(B):"";if(window.TIPTAP_DEBUG&&q&&(q.match(/<(ta-quotation|ta-sub|ta-border-sub|ta-epigraph|ta-selection-frame)[^>]*>/g),q.match(/<[^>]+class="[^"]*\b(ta-quotation|ta-sub|ta-border-sub|ta-epigraph|ta-selection-frame)\b[^"]*"[^>]*>/g),q.match(/<[^>]+class="[^"]*\b(lead|punchline)\b[^"]*"[^>]*>/g)),k=await n({element:M,content:q,editable:!1!==u.editable,placeholder:(0,o.t)(u.placeholder||"Начните писать..."),userId:u.userId||"anonymous",userName:u.userName||(0,o.t)("Гость"),userSlug:u.userSlug||null,userAvatar:u.userAvatar||null,userColor:u.userColor,collaboration:x,getLanguageToolPrivacyState:function(){const t=u.contentItem;if(!t)return{allowed:!0,reason:"no-content-item"};const e=t.status||null;return{allowed:!0,reason:null,status:e,isSharedDraft:!0===t.isSharedDraft,isPublicStatus:"posted"===e||"published"===e||"featured"===e||"recalled"===e}},uploadService:r,notificationService:c,onUpdate:(e,n)=>{C||S||n&&n.getMeta("remarkMarksInternal")||("synced"!==u.collabStatus&&(u.unsyncedChangesCount=(u.unsyncedChangesCount||0)+1),P&&clearTimeout(P),P=setTimeout(()=>{P=null,!S&&b&&e&&!e.isDestroyed&&t(()=>{b&&b.$setViewValue(e.getHTML())})},300))},onSelectionUpdate:e=>{!S&&u.onSelectionChange&&(E&&clearTimeout(E),E=setTimeout(()=>{E=null,!S&&u.onSelectionChange&&t(()=>{try{u.onSelectionChange({selection:e})}catch(t){}})},100))},onFocus:()=>{m.addClass("tiptap-focused"),u.onFocus&&t(()=>{try{u.onFocus()}catch(t){}})},onBlur:()=>{m.removeClass("tiptap-focused"),u.onBlur&&t(()=>{try{u.onBlur()}catch(t){}})}}),S){if(k)try{k.destroy()}catch(t){}return}if(!k)return void t(()=>{u.editorInstance=null});t(()=>{S||(u.editorInstance=k)});const Z=function(){S||!k||k.isDestroyed||setTimeout(function(){if(!S&&k&&!k.isDestroyed&&!k.isFocused&&k.isEditable){l();try{k.commands.focus("end")}catch(t){}}},50)};if(M.addEventListener("click",Z),u._containerClickHandler=Z,"undefined"!=typeof window&&window.DISCOURS_DEBUG_DOM){const t=M.querySelector(".ProseMirror");if(t&&!u._debugDomObserver){let e=0,n=Date.now();const i=[];u._debugDomObserver=new MutationObserver(t=>{e+=t.length;const s=(Date.now()-n)/1e3;e%50==0&&(e/s).toFixed(2),i.length<100&&t.forEach(t=>{i.push({type:t.type,target:t.target.nodeName+(t.target.className?"."+t.target.className.split(" ")[0]:""),added:t.addedNodes.length,removed:t.removedNodes.length,attr:t.attributeName})}),u._debugDomLogTimer||(u._debugDomLogTimer=setInterval(()=>{if(S)return;const t=(Date.now()-n)/1e3,s=((e/t).toFixed(2),{});i.forEach(t=>{const e=t.target+(t.attr?`[${t.attr}]`:"");s[e]=(s[e]||0)+1}),Object.entries(s).sort((t,e)=>e[1]-t[1]).slice(0,5)},5e3))}),u._debugDomObserver.observe(t,{childList:!0,subtree:!0,attributes:!0,characterData:!0})}}if(window.TIPTAP_DEBUG&&t(()=>{k.getHTML().match(/<(ta-quotation|ta-sub|ta-border-sub|ta-epigraph|ta-selection-frame)[^>]*>/g)},500),u.extractComments=()=>a(k),u.extractSuggestions=()=>v(k),u.generateId=y,x&&(u._latencyInterval=e(()=>{if(!S&&"synced"===u.collabStatus&&u.lastSyncTime){const t=Date.now()-u.lastSyncTime;u.connectionLatency=t<5e3?Math.min(t,200):t<3e4?300:600}},1e4)),b&&!x){let t=!0;b.$render=function(){if(k&&void 0!==b.$viewValue&&k.getHTML()!==b.$viewValue){C=!0;const e=_?_(b.$viewValue||""):(0,i.NT)(b.$viewValue||"");k.commands.setContent(e,!1),t&&(k.clearUndoHistory?k.clearUndoHistory():k.commands.clearHistory&&k.commands.clearHistory(),t=!1),C=!1}}}u.$watch("editable",t=>{k&&k.setEditable(!1!==t)});
1const j=u.$watch("userId",t=>{k&&t&&(k.setUserId(t),j())});u._tiptapEventBridges=["tiptap:addComment","tiptap:toggleSuggestionMode","tiptap:toggleSidebar","tiptap:togglePreview","tiptap:setEditingMode","tiptap:toggleShortcutsHelp","tiptap:toggleToc","tiptap:openHtmlEditor","tiptap:toggleDarkMode","tiptap:toggleQuietMode"].map(e=>{const n=n=>{t(()=>{u.$emit(e,n.detail)})};return M.addEventListener(e,n),{eventName:e,handler:n}})}catch(t){u.loadError="Ошибка инициализации редактора: "+(t.message||"Неизвестная ошибка"),I=!1,c.error("Не удалось инициализировать редактор")}}let k=null,C=!1,x=null,S=!1,I=!1;const M=m[0].querySelector(".tiptap-prosemirror-container");let P=null,E=null,L=null;u.isLoading=!0,u.loadError=null,u.isOfflineMode=!1,u.isReconnecting=!1,u.unsyncedChangesCount=0;let T=!1,A=!1,D=!1,V=null,H=null,B=null,R=null,U=null,O=null,N=null;const F=[],z=(0,a.Q)({schedule:(e,n)=>t(e,n),cancel:e=>t.cancel(e),limit:3,delay:2e3,retry:()=>{if(S||u.isOfflineMode)return;D=!1;const e=k&&k.getProvider&&k.getProvider();if(e&&"function"==typeof e.connect){u.collabStatus="connecting",V=t(()=>{V=null,S||u.isOfflineMode||"synced"===u.collabStatus||w("auth_retry_connection_timeout")},p),F.push(V);try{Promise.resolve(e.connect()).catch(()=>{S||u.isOfflineMode||"synced"===u.collabStatus||w("auth_retry_connect_error")})}catch(t){w("auth_retry_connect_error")}}else w("auth_retry_provider_missing")},exhausted:()=>{t(()=>{S||u.isOfflineMode||w("auth_failed_transient_limit")})}});u.retryInit=function(){u.loadError=null,u.isLoading=!0,u.isOfflineMode=!1,S=!1,I=!1,y()},u.retryCollabConnection=function(){if(!k||u.isReconnecting)return void l();B&&(t.cancel(B),B=null),l(),z.reset(),D=!1,u.isOfflineMode=!1,u.isReconnecting=!0,u.collabStatus="connecting";const e=k.getProvider&&k.getProvider();if(e&&"function"==typeof e.connect)return l(),V=t(()=>{!S&&k&&"connecting"===u.collabStatus&&(g("reconnection_timeout",{documentName:u.collabDocumentName,reason:"reconnection_failed",hadSynced:T}),u.isReconnecting=!1,w("reconnection_timeout"))},p),F.push(V),void e.connect();l();const n=k.getHTML();u.initialContent=n,A=!1,B=t(()=>{if(B=null,S)l();else try{k&&(k.destroy(),k=null),I=!1,u._waitedForContent=!0,_()}catch(t){u.isReconnecting=!1,w("reconnect_error"),c.error("Ошибка переподключения. Работаем в автономном режиме")}},100),F.push(B)},y(),u.$on("$destroy",()=>{S=!0,z.destroy(),u._containerClickHandler&&M&&(M.removeEventListener("click",u._containerClickHandler),u._containerClickHandler=null),u._debugDomObserver&&(u._debugDomObserver.disconnect(),u._debugDomObserver=null),u._debugDomLogTimer&&(clearInterval(u._debugDomLogTimer),u._debugDomLogTimer=null),P&&(clearTimeout(P),P=null),E&&(clearTimeout(E),E=null),V&&(t.cancel(V),V=null),H&&(t.cancel(H),H=null),B&&(t.cancel(B),B=null),U&&(clearTimeout(U),U=null),R&&(clearInterval(R),R=null),O&&(clearTimeout(O),O=null),N&&(clearTimeout(N),N=null),u._latencyInterval&&(e.cancel(u._latencyInterval),u._latencyInterval=null),F.forEach(e=>{e&&t.cancel(e)}),F.length=0,L&&(L(),L=null),u._tiptapEventBridges&&(u._tiptapEventBridges.forEach(({eventName:t,handler:e})=>{M.removeEventListener(t,e)}),u._tiptapEventBridges=null),k&&(k.destroy(),k=null)})}}}])}}]);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.