Skip to content

Commit a3ee428

Browse files
authored
Merge cca7cb0 into 7eba7bc
2 parents 7eba7bc + cca7cb0 commit a3ee428

14 files changed

Lines changed: 449 additions & 100 deletions

Rnwood.Smtp4dev/ApiModel/Server.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ public class Server
6767

6868
public string CurrentUserDefaultMailboxName { get; set; }
6969
public string HtmlValidateConfig { get; set; }
70+
public bool DisableHtmlValidation { get; set; }
71+
public bool DisableHtmlCompatibilityCheck { get; set; }
7072
public string CommandValidationExpression { get; set; }
7173
}
7274

Rnwood.Smtp4dev/ClientApp/src/components/messageclientanalysis.vue

Lines changed: 33 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,11 @@
1212
<div>Message has no HTML body</div>
1313
</div>
1414

15-
<el-table class="fill table" stripe :data="warnings" v-if="message?.hasHtmlBody" empty-text="There are no warnings">
15+
<div v-if="message?.hasHtmlBody && isHtmlCompatibilityCheckDisabled" class="fill nodetails centrecontents">
16+
<div>HTML compatibility check is disabled</div>
17+
</div>
18+
19+
<el-table class="fill table" stripe :data="warnings" v-if="message?.hasHtmlBody && !isHtmlCompatibilityCheckDisabled" empty-text="There are no warnings">
1620
<el-table-column prop="feature" label="Feature" width="180">
1721
<template #default="scope">
1822
<span style="font-family: Courier New, Courier, monospace">{{scope.row.feature}}</span>
@@ -41,25 +45,40 @@
4145
4246
import MessagesController from "../ApiClient/MessagesController";
4347
import Message from "../ApiClient/Message";
44-
import { doIUseEmail } from '@jsx-email/doiuse-email';
48+
import HubConnectionManager from "../ApiClient/HubConnectionManager";
49+
import { HtmlCompatibilityWorkerManager, type CompatibilityWarning } from "../workers/HtmlCompatibilityWorkerManager";
4550
4651
@Component
4752
class MessageClientAnalysis extends Vue {
4853
4954
@Prop({ default: null })
5055
message: Message | null | undefined;
5156
57+
@Prop({ default: null })
58+
connection: HubConnectionManager | null = null;
59+
5260
error: Error | null = null;
5361
loading = false;
62+
isHtmlCompatibilityCheckDisabled = false;
63+
private workerManager = new HtmlCompatibilityWorkerManager();
5464
55-
warnings: { message: string, feature: string, type: string, browsers: string[], url: string, isError: boolean }[] =[];
65+
warnings: CompatibilityWarning[] = [];
5666
5767
@Watch("message")
5868
async onMessageChanged(value: Message | null, oldValue: Message | null) {
5969
6070
await this.loadMessage();
6171
}
6272
73+
@Watch("connection")
74+
onConnectionChanged() {
75+
if (this.connection) {
76+
this.connection.onServerChanged( async () => {
77+
await this.loadMessage();
78+
});
79+
}
80+
}
81+
6382
@Watch("warnings")
6483
onWarningsChanged() {
6584
this.fireWarningCountChanged()
@@ -70,74 +89,24 @@
7089
return this.warnings?.length ?? 0;
7190
}
7291
73-
private parseWarning(warning: string, isError: boolean) {
74-
75-
const details = { message: warning, type: "", feature: "", browser: "", url: "", isError: false };
76-
const detailsMatch = warning.match(/^`(.+)` (support )?is (.+) (by|for) `(.+)`$/);
77-
78-
if (detailsMatch) {
79-
details.feature = detailsMatch[1] ?? null;
80-
details.type = detailsMatch[3] ?? null;
81-
details.browser = detailsMatch[5] ?? null;
82-
details.isError = isError;
83-
84-
if (details.feature.endsWith(" element")) {
85-
details.url = `https://www.caniemail.com/features/html-${details.feature.replace("<", "").replace("> element", "")}/`;
86-
} else {
87-
details.url = `https://www.caniemail.com/features/css-${details.feature.replace(":", "-")}/`;
88-
89-
}
90-
} else {
91-
details.type = warning;
92-
}
93-
94-
return details;
95-
}
96-
9792
async loadMessage() {
9893
9994
this.warnings = [];
10095
this.error = null;
10196
this.loading = true;
10297
10398
try {
104-
const newWarnings = [];
105-
if (this.message != null && this.message.hasHtmlBody) {
106-
107-
const html = await new MessagesController().getMessageHtml(this.message.id);
108-
const doIUseResults = doIUseEmail(html, { emailClients: ["*"] });
109-
110-
const allWarnings = [];
111-
for (const warning of doIUseResults.warnings) {
112-
const details = this.parseWarning(warning, false);
113-
allWarnings.push(details);
99+
if (this.message != null && this.message.hasHtmlBody && this.connection) {
100+
const server = await this.connection.getServer();
101+
this.isHtmlCompatibilityCheckDisabled = server.disableHtmlCompatibilityCheck;
102+
103+
if (!this.isHtmlCompatibilityCheckDisabled) {
104+
const html = await new MessagesController().getMessageHtml(this.message.id);
105+
106+
// Use web worker for compatibility checking
107+
const compatibilityResults = await this.workerManager.checkCompatibility(html);
108+
this.warnings = compatibilityResults;
114109
}
115-
116-
if (doIUseResults.success == false) {
117-
for (const warning of doIUseResults.errors) {
118-
const details = this.parseWarning(warning,true);
119-
allWarnings.push(details);
120-
}
121-
122-
}
123-
124-
const allGrouped = Object.groupBy(allWarnings, i => i.feature + " " + i.type);
125-
for (const groupKey in allGrouped) {
126-
const groupItems = allGrouped[groupKey]!;
127-
newWarnings.push({
128-
type: groupItems[0].type,
129-
130-
feature: groupItems[0].feature,
131-
message: groupItems[0].message,
132-
133-
url: groupItems[0].url,
134-
browsers: groupItems.map(i => i.browser).filter((value, index, array) => array.indexOf(value) === index),
135-
isError: groupItems[0].isError
136-
})
137-
}
138-
139-
this.warnings = newWarnings;
140-
141110
}
142111
} catch (e: any) {
143112
this.error = e;
@@ -152,7 +121,7 @@
152121
}
153122
154123
async destroyed() {
155-
124+
this.workerManager.destroy();
156125
}
157126
158127
}

Rnwood.Smtp4dev/ClientApp/src/components/messagehtmlvalidation.vue

Lines changed: 81 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -12,28 +12,46 @@
1212
<div>Message has no HTML body</div>
1313
</div>
1414

15-
<el-table class="fill table" stripe :data="warnings" v-if="message?.hasHtmlBody" empty-text="There are no warnings">
16-
<el-table-column prop="message" label="Message" width="200">
17-
<template #default="scope">
18-
<a target="_blank" :href="scope.row.ruleUrl">{{scope.row.message}}</a>
19-
</template>
20-
</el-table-column>
21-
<el-table-column width="50" Label="Loc">
22-
<template #default="scope">
23-
{{scope.row.line}}:{{scope.row.column}}
15+
<div v-if="message?.hasHtmlBody && isHtmlValidationDisabled" class="fill nodetails centrecontents">
16+
<div>HTML validation is disabled</div>
17+
</div>
18+
19+
<div v-if="message?.hasHtmlBody && !isHtmlValidationDisabled" class="vfillpanel">
20+
<el-table class="table" stripe :data="paginatedWarnings" empty-text="There are no warnings">
21+
<el-table-column prop="message" label="Message" width="200">
22+
<template #default="scope">
23+
<a target="_blank" :href="scope.row.ruleUrl">{{scope.row.message}}</a>
24+
</template>
25+
</el-table-column>
26+
<el-table-column width="50" Label="Loc">
27+
<template #default="scope">
28+
{{scope.row.line}}:{{scope.row.column}}
29+
</template>
30+
</el-table-column>
31+
<el-table-column prop="line" label="Source">
32+
<template #default="scope">
33+
<code style="display: block; white-space:pre; font-size: 9pt; height: 100%; width: 100%; overflow: auto;">
34+
{{this.html.split("\n")[scope.row.line-1]}}{{"\n"}}
35+
{{" ".repeat(Math.max(0, scope.row.column-2))}}{{"^".repeat(scope.row.size)}}
36+
</code>
37+
2438
</template>
25-
</el-table-column>
26-
<el-table-column prop="line" label="Source">
27-
<template #default="scope">
28-
<code style="display: block; white-space:pre; font-size: 9pt; height: 100%; width: 100%; overflow: auto;">
29-
{{this.html.split("\n")[scope.row.line-1]}}{{"\n"}}
30-
{{" ".repeat(Math.max(0, scope.row.column-2))}}{{"^".repeat(scope.row.size)}}
31-
</code>
32-
33-
</template>
34-
</el-table-column>
35-
36-
</el-table>
39+
</el-table-column>
40+
41+
</el-table>
42+
43+
<el-pagination
44+
v-if="warnings.length > pageSize"
45+
:current-page="currentPage"
46+
:page-size="pageSize"
47+
:page-sizes="[10, 25, 50, 100]"
48+
:total="warnings.length"
49+
layout="total, sizes, prev, pager, next, jumper"
50+
@size-change="handleSizeChange"
51+
@current-change="handleCurrentChange"
52+
style="margin-top: 10px; text-align: center;"
53+
/>
54+
</div>
3755
</div>
3856

3957

@@ -43,7 +61,9 @@
4361
4462
import MessagesController from "../ApiClient/MessagesController";
4563
import Message from "../ApiClient/Message";
46-
import { HtmlValidate, Message as HtmlValidateMessage } from "html-validate";
64+
import { Message as HtmlValidateMessage } from "html-validate";
65+
import HubConnectionManager from "../ApiClient/HubConnectionManager";
66+
import { HtmlValidationWorkerManager } from "../workers/HtmlValidationWorkerManager";
4767
4868
@Component
4969
class MessageHtmlValidation extends Vue {
@@ -54,8 +74,12 @@
5474
error: Error | null = null;
5575
loading = false;
5676
html = "";
57-
77+
5878
warnings: HtmlValidateMessage[] = [];
79+
currentPage = 1;
80+
pageSize = 25;
81+
isHtmlValidationDisabled = false;
82+
private workerManager = new HtmlValidationWorkerManager();
5983
6084
6185
@Prop({ default: null })
@@ -89,6 +113,27 @@
89113
return this.warnings?.length ?? 0;
90114
}
91115
116+
get paginatedWarnings() {
117+
const start = (this.currentPage - 1) * this.pageSize;
118+
const end = start + this.pageSize;
119+
return this.warnings.slice(start, end);
120+
}
121+
122+
handleSizeChange(newSize: number) {
123+
this.pageSize = newSize;
124+
this.currentPage = 1;
125+
}
126+
127+
handleCurrentChange(newPage: number) {
128+
this.currentPage = newPage;
129+
}
130+
131+
async refresh() {
132+
if (this.connection) {
133+
await this.loadMessage();
134+
}
135+
}
136+
92137
93138
94139
async loadMessage() {
@@ -97,20 +142,22 @@
97142
this.error = null;
98143
this.loading = true;
99144
this.html = "";
145+
this.currentPage = 1;
100146
101147
try {
102148
const newWarnings = [];
103-
if (this.message != null && this.message.hasHtmlBody) {
104-
105-
this.html = await new MessagesController().getMessageHtml(this.message.id);
106-
const config = JSON.parse((await this.connection.getServer()).htmlValidateConfig);
107-
108-
const report = await new HtmlValidate(config).validateString(this.html, "messagebody");
109-
for (const r of report.results) {
110-
newWarnings.push(...r.messages);
149+
if (this.message != null && this.message.hasHtmlBody && this.connection) {
150+
const server = await this.connection.getServer();
151+
this.isHtmlValidationDisabled = server.disableHtmlValidation;
152+
153+
if (!this.isHtmlValidationDisabled) {
154+
this.html = await new MessagesController().getMessageHtml(this.message.id);
155+
const config = JSON.parse(server.htmlValidateConfig);
156+
157+
// Use web worker for validation
158+
const validationResults = await this.workerManager.validateHtml(this.html, config);
159+
newWarnings.push(...validationResults);
111160
}
112-
113-
114161
}
115162
this.warnings = newWarnings;
116163
} catch (e: any) {
@@ -126,7 +173,7 @@
126173
}
127174
128175
async destroyed() {
129-
176+
this.workerManager.destroy();
130177
}
131178
132179
}

Rnwood.Smtp4dev/ClientApp/src/components/messageview.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@
129129
<el-tag v-if="analysisWarningCount.clients" style="margin-left: 6px;" type="warning" size="small" effect="dark" round><el-icon><WarnTriangleFilled /></el-icon> {{analysisWarningCount.clients ? analysisWarningCount.clients : ''}}</el-tag>
130130

131131
</template>
132-
<messageclientanalysis class="fill" :message="message" @warning-count-changed="n => this.analysisWarningCount.clients=n"></messageclientanalysis>
132+
<messageclientanalysis class="fill" :connection="connection" :message="message" @warning-count-changed="n => this.analysisWarningCount.clients=n"></messageclientanalysis>
133133
</el-tab-pane>
134134

135135
<el-tab-pane label="HTML Validation" id="html" class="hfillpanel">

Rnwood.Smtp4dev/ClientApp/src/components/settingsdialog.vue

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,18 @@
6262

6363
<el-switch v-model="server.disableMessageSanitisation" :disabled="server.lockedSettings.disableMessageSanitisation" />
6464
</el-form-item>
65+
66+
<el-form-item label="Disable HTML validation in Analysis tab" prop="server.disableHtmlValidation">
67+
<el-icon v-if="server.lockedSettings.disableHtmlValidation" :title="`Locked: ${server.lockedSettings.disableHtmlValidation}`"><Lock /></el-icon>
68+
69+
<el-switch v-model="server.disableHtmlValidation" :disabled="server.lockedSettings.disableHtmlValidation" />
70+
</el-form-item>
71+
72+
<el-form-item label="Disable HTML compatibility checks in Analysis tab" prop="server.disableHtmlCompatibilityCheck">
73+
<el-icon v-if="server.lockedSettings.disableHtmlCompatibilityCheck" :title="`Locked: ${server.lockedSettings.disableHtmlCompatibilityCheck}`"><Lock /></el-icon>
74+
75+
<el-switch v-model="server.disableHtmlCompatibilityCheck" :disabled="server.lockedSettings.disableHtmlCompatibilityCheck" />
76+
</el-form-item>
6577
</el-tab-pane>
6678
<el-tab-pane label="SMTP Server">
6779

0 commit comments

Comments
 (0)