@hmcts/ccd-case-ui-toolkit 7.2.58 → 7.2.59-markdown-add-validation
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/esm2022/lib/shared/components/palette/document/write-document-field.component.mjs +10 -5
- package/esm2022/lib/shared/directives/substitutor/label-substitutor.directive.mjs +4 -6
- package/esm2022/lib/shared/services/document-management/document-management.service.mjs +1 -2
- package/esm2022/lib/shared/services/form/form-validators.service.mjs +76 -3
- package/fesm2022/hmcts-ccd-case-ui-toolkit.mjs +87 -13
- package/fesm2022/hmcts-ccd-case-ui-toolkit.mjs.map +1 -1
- package/lib/shared/components/palette/document/write-document-field.component.d.ts +1 -0
- package/lib/shared/components/palette/document/write-document-field.component.d.ts.map +1 -1
- package/lib/shared/directives/substitutor/label-substitutor.directive.d.ts.map +1 -1
- package/lib/shared/services/document-management/document-management.service.d.ts.map +1 -1
- package/lib/shared/services/form/form-validators.service.d.ts +5 -0
- package/lib/shared/services/form/form-validators.service.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -8,7 +8,7 @@ import * as i1$1 from '@angular/router';
|
|
|
8
8
|
import { RouterModule, NavigationStart, NavigationEnd } from '@angular/router';
|
|
9
9
|
import * as i4 from '@angular/forms';
|
|
10
10
|
import { NG_VALUE_ACCESSOR, NG_VALIDATORS, FormArray, FormGroup, FormControl, Validators, ReactiveFormsModule, FormsModule } from '@angular/forms';
|
|
11
|
-
import { BehaviorSubject, throwError, Subject, EMPTY, Observable, of, combineLatest, timer, forkJoin, fromEvent, Subscription } from 'rxjs';
|
|
11
|
+
import { BehaviorSubject, throwError, Subject, EMPTY, Observable, skip, of, combineLatest, timer, forkJoin, fromEvent, Subscription } from 'rxjs';
|
|
12
12
|
import * as i1$2 from '@angular/common/http';
|
|
13
13
|
import { HttpErrorResponse, HttpHeaders, HttpParams } from '@angular/common/http';
|
|
14
14
|
import { distinctUntilChanged, catchError, map, publish, refCount, switchMap, debounceTime, take, delay, finalize, timeout, mergeMap, retryWhen, tap, delayWhen, publishReplay, first, takeUntil, filter } from 'rxjs/operators';
|
|
@@ -5093,10 +5093,15 @@ class FormValidatorsService {
|
|
|
5093
5093
|
return validator;
|
|
5094
5094
|
}
|
|
5095
5095
|
static markDownPatternValidator() {
|
|
5096
|
-
|
|
5096
|
+
// Matches: [text](url), , <img ...>, <a ...>...</a>
|
|
5097
|
+
const inlineMarkdownPattern = /(?:!?\[[^\]]{0,500}\]\([^)]{0,500}\)|<(?:img\b[^>]{0,500}>|a\b[^>]{0,500}>[\s\S]*?<\/a>))/i;
|
|
5098
|
+
// Matches: [text][id], ![alt][id], and the collapsed form [text][]
|
|
5099
|
+
const referenceBoxPattern = /(!)?\[((?:[^[\]\\]|\\.){0,500})\]\s*\[([^\]]{0,100})\]/;
|
|
5100
|
+
// Matches: autolinks such as <http://example.com>
|
|
5101
|
+
const autolinkPattern = /<(?:[A-Za-z][A-Za-z0-9+.-]*:[^ <>\n]*|[A-Za-z0-9.!#$%&'*+/=?^_`{|}~-]+@[A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)+)>/;
|
|
5097
5102
|
return (control) => {
|
|
5098
5103
|
const value = control?.value?.toString().trim();
|
|
5099
|
-
return (value &&
|
|
5104
|
+
return (value && (inlineMarkdownPattern.test(value) || referenceBoxPattern.test(value) || this.matchesReferenceUrlDef(value) || autolinkPattern.test(value) || this.hasMultiBracket(value))) ? { markDownPattern: {} } : null;
|
|
5100
5105
|
};
|
|
5101
5106
|
}
|
|
5102
5107
|
// TODO: Strip this out as it's only here for the moment because
|
|
@@ -5114,6 +5119,74 @@ class FormValidatorsService {
|
|
|
5114
5119
|
}
|
|
5115
5120
|
return control;
|
|
5116
5121
|
}
|
|
5122
|
+
// Check for multi-bracket markdown links and validate destination URL
|
|
5123
|
+
static hasMultiBracket(value) {
|
|
5124
|
+
// Sonar-friendly detector: opening-run + text + first closing ']'
|
|
5125
|
+
const openingTextClosePattern = /\[{1,10}[^[\]\n]{1,60}\]/;
|
|
5126
|
+
// Can add an additional RegEx for additional URL validation rules if needed here
|
|
5127
|
+
let scanIndex = 0;
|
|
5128
|
+
const totalLength = value.length;
|
|
5129
|
+
while (scanIndex < totalLength) {
|
|
5130
|
+
const seg = this.findOpeningTextClose(value, scanIndex, openingTextClosePattern);
|
|
5131
|
+
if (!seg) {
|
|
5132
|
+
return false; // no candidate -> no match
|
|
5133
|
+
}
|
|
5134
|
+
const runs = this.extendClosingRunAndRequireParen(value, seg.absStart, seg.afterFirstClose);
|
|
5135
|
+
// if there is more than one opening '[' and there is at least a matching number of closing ']'
|
|
5136
|
+
if (runs && runs.openingRunCount > 1 && runs.openingRunCount === runs.closingRunCount) {
|
|
5137
|
+
// If there were additional validation rules, they would be applied here
|
|
5138
|
+
return true;
|
|
5139
|
+
}
|
|
5140
|
+
// Advance to avoid stalling on overlaps
|
|
5141
|
+
scanIndex = seg.absStart + 1;
|
|
5142
|
+
}
|
|
5143
|
+
return false;
|
|
5144
|
+
}
|
|
5145
|
+
// Find opening '[' run, text, and first closing ']'
|
|
5146
|
+
static findOpeningTextClose(source, fromIndex, pattern) {
|
|
5147
|
+
const slice = source.slice(fromIndex);
|
|
5148
|
+
const match = pattern.exec(slice);
|
|
5149
|
+
if (!match) {
|
|
5150
|
+
return null;
|
|
5151
|
+
}
|
|
5152
|
+
const absStart = fromIndex + match.index;
|
|
5153
|
+
const afterFirstClose = absStart + match[0].length; // index just after the first ']'
|
|
5154
|
+
return { absStart, afterFirstClose };
|
|
5155
|
+
}
|
|
5156
|
+
// Count opening '[' run, extend the ']' run, and require '(' right after the full ']' run
|
|
5157
|
+
static extendClosingRunAndRequireParen(source, absStart, afterFirstClose) {
|
|
5158
|
+
const n = source.length;
|
|
5159
|
+
// Count opening '[' run (e.g., '[[[')
|
|
5160
|
+
let openingRunCount = 0;
|
|
5161
|
+
for (let i = absStart; i < n && source[i] === '['; i++) {
|
|
5162
|
+
openingRunCount++;
|
|
5163
|
+
}
|
|
5164
|
+
// Extend closing ']' run forward from the first one
|
|
5165
|
+
let closingRunCount = 1;
|
|
5166
|
+
let afterClosingRun = afterFirstClose;
|
|
5167
|
+
while (afterClosingRun < n && source[afterClosingRun] === ']') {
|
|
5168
|
+
closingRunCount++;
|
|
5169
|
+
afterClosingRun++;
|
|
5170
|
+
}
|
|
5171
|
+
return { openingRunCount, closingRunCount, afterOpenParen: afterClosingRun + 1 };
|
|
5172
|
+
}
|
|
5173
|
+
static isValidReferenceUrlTitleTail(tail) {
|
|
5174
|
+
const possibleTitle = tail.trim();
|
|
5175
|
+
// Accept exactly one of: "title", 'title', (title) — bounded and single-line.
|
|
5176
|
+
if (!possibleTitle || /^"[^"\r\n]{0,300}"$/.test(possibleTitle) || /^'[^'\r\n]{0,300}'$/.test(possibleTitle) || /^\([^)\r\n]{0,300}\)$/.test(possibleTitle)) {
|
|
5177
|
+
return true;
|
|
5178
|
+
}
|
|
5179
|
+
return false;
|
|
5180
|
+
}
|
|
5181
|
+
static matchesReferenceUrlDef(line) {
|
|
5182
|
+
// Single-line, pragmatic CommonMark-style reference definition e.g. [text]: http://example.com
|
|
5183
|
+
const baseReferenceUrlPattern = /^[ \t]{0,3}\[([^\]]{1,100})\]:[ \t]*<?([^\s>]{1,2048})>?[ \t]*([^ \t\r\n].*)?$/m;
|
|
5184
|
+
const mainRegEx = baseReferenceUrlPattern.exec(line);
|
|
5185
|
+
if (!mainRegEx)
|
|
5186
|
+
return false;
|
|
5187
|
+
const tail = mainRegEx[3] ?? "";
|
|
5188
|
+
return this.isValidReferenceUrlTitleTail(tail);
|
|
5189
|
+
}
|
|
5117
5190
|
static ɵfac = function FormValidatorsService_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || FormValidatorsService)(); };
|
|
5118
5191
|
static ɵprov = /*@__PURE__*/ i0.ɵɵdefineInjectable({ token: FormValidatorsService, factory: FormValidatorsService.ɵfac });
|
|
5119
5192
|
}
|
|
@@ -6533,11 +6606,8 @@ class LabelSubstitutorDirective {
|
|
|
6533
6606
|
this.initialHintText = this.caseField.hint_text;
|
|
6534
6607
|
this.caseField.originalLabel = this.caseField.label;
|
|
6535
6608
|
this.formGroup = this.formGroup || new FormGroup({});
|
|
6536
|
-
this.languageSubscription = this.rpxTranslationService.language$.subscribe(() => {
|
|
6537
|
-
|
|
6538
|
-
setTimeout(() => {
|
|
6539
|
-
this.onLanguageChange();
|
|
6540
|
-
}, 100);
|
|
6609
|
+
this.languageSubscription = this.rpxTranslationService.language$.pipe(skip(1)).subscribe(() => {
|
|
6610
|
+
this.onLanguageChange();
|
|
6541
6611
|
});
|
|
6542
6612
|
this.applySubstitutions();
|
|
6543
6613
|
}
|
|
@@ -7346,7 +7416,6 @@ class DocumentManagementService {
|
|
|
7346
7416
|
isDocumentSecureModeEnabled() {
|
|
7347
7417
|
const documentSecureModeCaseTypeExclusions = this.appConfig.getCdamExclusionList()?.split(',');
|
|
7348
7418
|
const isDocumentOnExclusionList = documentSecureModeCaseTypeExclusions?.includes(this.caseTypeId);
|
|
7349
|
-
this.appConfig.logMessage(`isDocumentOnExclusionList: ${isDocumentOnExclusionList}`);
|
|
7350
7419
|
if (!isDocumentOnExclusionList) {
|
|
7351
7420
|
return true;
|
|
7352
7421
|
}
|
|
@@ -15252,6 +15321,7 @@ class WriteDocumentFieldComponent extends AbstractFieldWriteComponent {
|
|
|
15252
15321
|
jurisdictionId;
|
|
15253
15322
|
caseTypeId;
|
|
15254
15323
|
caseTypeExclusions;
|
|
15324
|
+
caseId;
|
|
15255
15325
|
// Should the file upload use CDAM
|
|
15256
15326
|
fileSecureModeOn = false;
|
|
15257
15327
|
constructor(appConfig, caseNotifier, documentManagement, dialog, fileUploadStateService, jurisdictionService) {
|
|
@@ -15272,6 +15342,7 @@ class WriteDocumentFieldComponent extends AbstractFieldWriteComponent {
|
|
|
15272
15342
|
if (caseDetails) {
|
|
15273
15343
|
this.caseTypeId = caseDetails?.case_type?.id;
|
|
15274
15344
|
this.jurisdictionId = caseDetails?.case_type?.jurisdiction?.id;
|
|
15345
|
+
this.caseId = caseDetails?.case_id;
|
|
15275
15346
|
}
|
|
15276
15347
|
if (jurisdiction) {
|
|
15277
15348
|
this.jurisdictionId = jurisdiction.id;
|
|
@@ -15290,12 +15361,8 @@ class WriteDocumentFieldComponent extends AbstractFieldWriteComponent {
|
|
|
15290
15361
|
}
|
|
15291
15362
|
// use the documentManagement service to check if the document upload should use CDAM
|
|
15292
15363
|
if (this.documentManagement.isDocumentSecureModeEnabled()) {
|
|
15293
|
-
this.appConfig.logMessage(`CDAM is enabled for case with case ref:: ${caseDetails?.case_id}`);
|
|
15294
15364
|
this.fileSecureModeOn = true;
|
|
15295
15365
|
}
|
|
15296
|
-
else {
|
|
15297
|
-
this.appConfig.logMessage(`CDAM is disabled for case with case ref:: ${caseDetails?.case_id}`);
|
|
15298
|
-
}
|
|
15299
15366
|
this.dialogConfig = initDialog();
|
|
15300
15367
|
let document = this.caseField.value || { document_url: null, document_binary_url: null, document_filename: null };
|
|
15301
15368
|
document = this.fileSecureModeOn && !document.document_hash ? { ...document, document_hash: null } : document;
|
|
@@ -15529,6 +15596,13 @@ class WriteDocumentFieldComponent extends AbstractFieldWriteComponent {
|
|
|
15529
15596
|
return documentUpload;
|
|
15530
15597
|
}
|
|
15531
15598
|
handleDocumentUploadResult(result) {
|
|
15599
|
+
// use the documentManagement service to check if the document upload should use CDAM
|
|
15600
|
+
if (this.documentManagement.isDocumentSecureModeEnabled()) {
|
|
15601
|
+
this.appConfig.logMessage(`CDAM is enabled for case with case ref:: ${this.caseId}`);
|
|
15602
|
+
}
|
|
15603
|
+
else {
|
|
15604
|
+
this.appConfig.logMessage(`CDAM is disabled for case with case ref:: ${this.caseId}`);
|
|
15605
|
+
}
|
|
15532
15606
|
if (!this.uploadedDocument) {
|
|
15533
15607
|
if (this.fileSecureModeOn) {
|
|
15534
15608
|
this.createDocumentForm({ document_url: null, document_binary_url: null, document_filename: null, document_hash: null });
|