Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 72 additions & 2 deletions DigitalLearningSolutions.Web/Scripts/frameworks/htmleditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,14 +70,19 @@ if (jodited === false) {
const clean = DOMPurify.sanitize(editor.editor.innerHTML);
editor.editor.innerHTML = clean;
});

document.addEventListener('DOMContentLoaded', () => {
removeWaveErrors();
removeDevToolsIssues();
});

// ** Start* for jodit editor error (display red outline, focus on summary error text click) ****
const textarea = document.querySelector('.nhsuk-textarea.html-editor.nhsuk-input--error') as HTMLTextAreaElement | null;
if (textarea) {
const editorDiv = document.querySelector('.jodit-container.jodit.jodit_theme_default.jodit-wysiwyg_mode') as HTMLDivElement | null;
editorDiv?.classList.add('jodit-container', 'jodit', 'jodit_theme_default', 'jodit-wysiwyg_mode', 'jodit-error');
}

const summary = document.querySelector('.nhsuk-list.nhsuk-error-summary__list') as HTMLDivElement | null;

if (summary) {
summary.addEventListener('click', (e: Event) => {
if (textarea) {
Expand All @@ -96,5 +101,70 @@ if (jodited === false) {
}
});
}
// ** End* for jodit editor error (display red outline, focus on summary error text click) ****
}
}

function removeWaveErrors() {
const input = Array.from(document.querySelectorAll<HTMLInputElement>('input[tab-index="-1"]'))
.find((el) => el.style.width === '0px' && el.style.height === '0px'
&& el.style.position === 'absolute' && el.style.visibility === 'hidden');

if (input) {
input.setAttribute('aria-label', 'Hidden input for accessibility');
input.setAttribute('title', 'HiddenInput');
}

const observer = new MutationObserver((mutations, obs) => {
const textarea = document.querySelector('.ace_text-input') as HTMLTextAreaElement | null;
if (textarea) {
textarea.setAttribute('aria-label', 'ace_text-input');
obs.disconnect();
}
});
observer.observe(document.body, {
childList: true,
subtree: true,
});
}
function removeDevToolsIssues() {
// set role = 'list' to toolbar
const toolbarbox = document.querySelector('.jodit-toolbar__box') as HTMLElement | null;
if (toolbarbox) {
toolbarbox.setAttribute('role', 'list');
}
// set role = 'list' to statusbar
const statusbar = document.querySelector('.jodit-xpath') as HTMLElement | null;
if (statusbar) {
statusbar.setAttribute('role', 'list');
}
document.querySelectorAll('.jodit-toolbar-button__trigger').forEach((el) => {
el.removeAttribute('role');
});
// observer to detect role='trigger' and remove role
const observer = new MutationObserver(() => {
document.querySelectorAll('.jodit-toolbar-button__trigger').forEach((el) => {
el.removeAttribute('role');
});
});
const target = document.querySelector('.jodit-toolbar__box');
if (target) {
observer.observe(target, { subtree: true, childList: true });
}

// observer to detect iframe and set title
const observer2 = new MutationObserver(() => {
const hiddenIframe = Array.from(document.querySelectorAll('iframe')).find((iframe) => {
const rect = iframe.getBoundingClientRect();
return rect.width === 0 && rect.height === 0 && (iframe.src === 'about:blank' || iframe.getAttribute('src') === 'about:blank');
});
if (hiddenIframe) {
hiddenIframe.setAttribute('title', 'Hidden iframe');
observer2.disconnect(); // Stop observing once found
}
});
observer2.observe(document.body, {
childList: true,
subtree: true,
});
}
3 changes: 3 additions & 0 deletions DigitalLearningSolutions.Web/Styles/jodit.scss
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,6 @@
border: 2px solid red !important;
border-radius: 4px;
}
.jodit-placeholder {
display: none !important;
}
Original file line number Diff line number Diff line change
@@ -1,106 +1,106 @@
@using DigitalLearningSolutions.Web.ViewModels.Frameworks;
@model FrameworkCompetencyViewModel;
@{
ViewData["Title"] = !string.IsNullOrWhiteSpace(Model.VocabSingular()) ? Model.VocabSingular(): "Competency";
ViewData["Application"] = "Framework Service";
ViewData["HeaderPathName"] = "Framework Service";
ViewData["Title"] = !string.IsNullOrWhiteSpace(Model.VocabSingular()) ? Model.VocabSingular() : "Competency";
ViewData["Application"] = "Framework Service";
ViewData["HeaderPathName"] = "Framework Service";
}
<link rel="stylesheet" href="@Url.Content("~/css/frameworks/frameworksShared.css")" asp-append-version="true">
<link rel="stylesheet" href="@Url.Content("~/css/jodit.css")" asp-append-version="true">
@section NavMenuItems {
<partial name="Shared/_NavMenuItems" />
<partial name="Shared/_NavMenuItems" />
}
@section NavBreadcrumbs {
<nav class="nhsuk-breadcrumb" aria-label="Breadcrumb">
<div class="nhsuk-width-container">
<ol class="nhsuk-breadcrumb__list">
<li class="nhsuk-breadcrumb__item"><a class="nhsuk-breadcrumb__link trigger-loader" asp-action="ViewFrameworks" asp-route-tabname="Mine">Frameworks</a></li>
<li class="nhsuk-breadcrumb__item"><a class="nhsuk-breadcrumb__link trigger-loader" asp-action="ViewFramework" asp-route-frameworkCompetencyGroupId="@Model.FrameworkCompetencyGroupId" asp-fragment="[email protected]" asp-route-frameworkId="@Model.DetailFramework.ID" asp-route-tabname="Structure">Framework Structure</a></li>
<li class="nhsuk-breadcrumb__item">@Model.VocabSingular()</li>
</ol>
@section NavBreadcrumbs {
<nav class="nhsuk-breadcrumb" aria-label="Breadcrumb">
<div class="nhsuk-width-container">
<ol class="nhsuk-breadcrumb__list">
<li class="nhsuk-breadcrumb__item"><a class="nhsuk-breadcrumb__link trigger-loader" asp-action="ViewFrameworks" asp-route-tabname="Mine">Frameworks</a></li>
<li class="nhsuk-breadcrumb__item"><a class="nhsuk-breadcrumb__link trigger-loader" asp-action="ViewFramework" asp-route-frameworkCompetencyGroupId="@Model.FrameworkCompetencyGroupId" asp-fragment="[email protected]" asp-route-frameworkId="@Model.DetailFramework.ID" asp-route-tabname="Structure">Framework Structure</a></li>
<li class="nhsuk-breadcrumb__item">@Model.VocabSingular()</li>
</ol>
<p class="nhsuk-breadcrumb__back"><a class="nhsuk-breadcrumb__backlink" asp-action="ViewFramework" asp-route-frameworkCompetencyGroupId="@Model.FrameworkCompetencyGroupId" asp-fragment="[email protected]" asp-route-frameworkId="@Model.DetailFramework.ID" asp-route-tabname="Structure">Back to framework structure</a></p>
</div>
</nav>
</div>
</nav>
}
<div class="nhsuk-grid-row">
<div class="nhsuk-grid-row">

<div class="nhsuk-grid-column-full">
@if (Model.FrameworkCompetency.Id > 0)
{
<h1>
Edit @Model.VocabSingular().ToLower()
</h1>
}
else
{
<h1>
Add new @Model.VocabSingular().ToLower()
</h1>
}
@if (Model.DetailFramework.PublishStatusID == 3)
{
<partial name="Shared/_PublishedWarning" />
}
<form method="post">
@if (!ViewData.ModelState.IsValid)
{
<partial name="_ErrorSummary" />
}
<nhs-form-group nhs-validation-for="FrameworkCompetency.Name">
<label class="nhsuk-label" id="competency-name-label" for="competency-name">
@Model.VocabSingular() statement
</label>
<span nhs-validation-for="FrameworkCompetency.Name"></span>
<input class="nhsuk-input" asp-for="FrameworkCompetency.Name" id="Name" name="Name" type="text" error-class-toggle="nhsuk-input--error" aria-describedby="competency-name-label">
</nhs-form-group>
<div class="nhsuk-form-group">
<vc:text-area asp-for="FrameworkCompetency.Description"
label="@Model.VocabSingular() description"
populate-with-current-value="true"
rows="5"
spell-check="false"
hint-text=""
css-class="html-editor"
character-count="null"
populate-with-current-value="true" />
</div>
@if (Model.CompetencyFlags?.Count() > 0)
{
<div class="nhsuk-form-group">
<label class="nhsuk-label nhsuk-u-margin-bottom-2" id="competency-description-label" for="competency-description">
@Model.VocabSingular() tags
</label>
@foreach (var flag in Model.CompetencyFlags)
{
<div class="nhsuk-checkboxes__item">
<input class="nhsuk-checkboxes__input" name="selectedFlagIds" type="checkbox" value="@flag.FlagId" @(flag.Selected ? "checked" : string.Empty) />
<label class="nhsuk-label nhsuk-checkboxes__label">
@flag.FlagName
</label>
@if (Model.FrameworkCompetency.Id > 0)
{
<h1>
Edit @Model.VocabSingular().ToLower()
</h1>
}
else
{
<h1>
Add new @Model.VocabSingular().ToLower()
</h1>
}
@if (Model.DetailFramework.PublishStatusID == 3)
{
<partial name="Shared/_PublishedWarning" />
}
<form method="post">
@if (!ViewData.ModelState.IsValid)
{
<partial name="_ErrorSummary" />
}
<nhs-form-group nhs-validation-for="FrameworkCompetency.Name">
<label class="nhsuk-label" id="competency-name-label" for="name">
@Model.VocabSingular() statement
</label>
<span nhs-validation-for="FrameworkCompetency.Name"></span>
<input class="nhsuk-input" asp-for="FrameworkCompetency.Name" id="Name" name="Name" type="text" error-class-toggle="nhsuk-input--error" aria-describedby="competency-name-label">
</nhs-form-group>
<div class="nhsuk-form-group">
<vc:text-area asp-for="FrameworkCompetency.Description"
label="@Model.VocabSingular() description"
populate-with-current-value="true"
rows="5"
spell-check="false"
hint-text=""
css-class="html-editor"
character-count="null"
populate-with-current-value="true" />
</div>
}
</div>
}
@if (Model.CompetencyFlags?.Count() > 0)
{
<div class="nhsuk-form-group">
<label class="nhsuk-label nhsuk-u-margin-bottom-2" id="competency-description-label" for="competency-description">
@Model.VocabSingular() tags
</label>
@foreach (var flag in Model.CompetencyFlags)
{
<div class="nhsuk-checkboxes__item">
<input class="nhsuk-checkboxes__input" name="selectedFlagIds" type="checkbox" value="@flag.FlagId" @(flag.Selected ? "checked" : string.Empty) />
<label class="nhsuk-label nhsuk-checkboxes__label">
@flag.FlagName
</label>
</div>
}
</div>
}

<input name="Id" type="hidden" asp-for="FrameworkCompetency.Id" />
<input name="CompetencyID" type="hidden" asp-for="FrameworkCompetency.CompetencyID" />
<input name="Ordering" type="hidden" asp-for="FrameworkCompetency.Ordering" />
<input name="Id" type="hidden" asp-for="FrameworkCompetency.Id" />
<input name="CompetencyID" type="hidden" asp-for="FrameworkCompetency.CompetencyID" />
<input name="Ordering" type="hidden" asp-for="FrameworkCompetency.Ordering" />

<button class="nhsuk-button" type="submit">
Save
</button>
<div class="nhsuk-back-link">
<a class="nhsuk-back-link__link" asp-action="ViewFramework" asp-route-frameworkCompetencyGroupId="@Model.FrameworkCompetencyGroupId" asp-fragment="[email protected]" asp-route-frameworkId="@Model.DetailFramework.ID" asp-route-tabname="Structure">
<svg class="nhsuk-icon nhsuk-icon__chevron-left" focusable='false' xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true" focusable="false">
<path d="M13.41 12l5.3-5.29a1 1 0 1 0-1.42-1.42L12 10.59l-5.29-5.3a1 1 0 0 0-1.42 1.42l5.3 5.29-5.3 5.29a1 1 0 0 0 0 1.42 1 1 0 0 0 1.42 0l5.29-5.3 5.29 5.3a1 1 0 0 0 1.42 0 1 1 0 0 0 0-1.42z"></path>
</svg>
Cancel
</a>
</div>
</form>
</div>
<button class="nhsuk-button" type="submit">
Save
</button>
<div class="nhsuk-back-link">
<a class="nhsuk-back-link__link" asp-action="ViewFramework" asp-route-frameworkCompetencyGroupId="@Model.FrameworkCompetencyGroupId" asp-fragment="[email protected]" asp-route-frameworkId="@Model.DetailFramework.ID" asp-route-tabname="Structure">
<svg class="nhsuk-icon nhsuk-icon__chevron-left" focusable='false' xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true" focusable="false">
<path d="M13.41 12l5.3-5.29a1 1 0 1 0-1.42-1.42L12 10.59l-5.29-5.3a1 1 0 0 0-1.42 1.42l5.3 5.29-5.3 5.29a1 1 0 0 0 0 1.42 1 1 0 0 0 1.42 0l5.29-5.3 5.29 5.3a1 1 0 0 0 1.42 0 1 1 0 0 0 0-1.42z"></path>
</svg>
Cancel
</a>
</div>
</form>
</div>
</div>
@section scripts {
<script src="@Url.Content("~/js/frameworks/htmleditor.js")" asp-append-version="true"></script>
<script src="@Url.Content("~/js/frameworks/htmleditor.js")" asp-append-version="true"></script>
}

Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,19 @@
{
<text>
<li>
Some existing @Model.FrameworkVocabularySingular.ToLower() @(Model.CompetenciesToReorderCount == 1 ? "record" : "records") have changed sequence in your uploaded sheet. You can choose whether to fix them in the new order next.
Some existing @Model.FrameworkVocabularySingular.ToLower() @(Model.CompetenciesToReorderCount == 1 ? "record" : "records") have changed sequence in your uploaded sheet. You can choose whether to fix them in the new order next.
</li></text>
}
<li>No errors</li>
}
</ul>
@if (Model.ErrorCount == 0)
{
<a asp-controller="Frameworks" role="button" asp-route-frameworkId="@ViewContext.RouteData.Values["frameworkId"]" asp-route-tabname="Structure" asp-action="ApplyCompetencyOrdering" class="nhsuk-button">Continue</a>
<a asp-controller="Frameworks" role="button" asp-route-frameworkId="@ViewContext.RouteData.Values["frameworkId"]"
asp-route-tabname="Structure" asp-action="ApplyCompetencyOrdering" class="nhsuk-button">
Continue
<span class="nhsuk-u-visually-hidden">to apply sequence changes</span>
</a>
}
else
{
Expand Down
Loading