Skip to content

Commit

Permalink
Implement warning dialog
Browse files Browse the repository at this point in the history
  • Loading branch information
HashidaTKS committed Sep 20, 2024
1 parent 5505183 commit decfc74
Show file tree
Hide file tree
Showing 4 changed files with 177 additions and 93 deletions.
2 changes: 1 addition & 1 deletion manifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
<ExtensionPoint xsi:type="LaunchEvent">
<LaunchEvents>
<LaunchEvent Type="OnNewMessageCompose" FunctionName="onNewMessageComposeCreated"/>
<LaunchEvent Type="OnMessageSend" FunctionName="onMessageSend" SendMode="Block"/>
<LaunchEvent Type="OnMessageSend" FunctionName="onMessageSend" SendMode="SoftBlock"/>
</LaunchEvents>
<SourceLocation resid="residUILessFunctionFileUrl"/>
</ExtensionPoint>
Expand Down
7 changes: 5 additions & 2 deletions src/web/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,10 +101,13 @@ function getAllData(callback) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
function onMessageSend(event) {
getAllData(function () {
console.debug(data);
Office.context.ui.displayDialogAsync(
window.location.origin + "/dialog.html",
{ asyncContext: event },
{
asyncContext: event,
height: 60,
width: 60,
},
function (asyncResult) {
const dialog = asyncResult.value;
dialog.addEventHandler(Office.EventType.DialogMessageReceived, (arg) => {
Expand Down
44 changes: 22 additions & 22 deletions src/web/dialog.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,35 +3,35 @@
<head>
<title>Confirmation</title>
<meta http-equiv="X-UA-Compatible" content="IE=Edge">
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM" crossorigin="anonymous">
<!-- <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM" crossorigin="anonymous"> -->
<script type="module" src="https://unpkg.com/@fluentui/web-components"></script>
<script src="https://appsforoffice.microsoft.com/lib/1/hosted/office.js" type="text/javascript" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.min.js" type="application/javascript" crossorigin="anonymous"></script>
<script src="https://code.jquery.com/jquery-3.7.1.min.js" integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo=" crossorigin="anonymous"></script>
<script src="dialog.js" type="application/javascript"></script>
</script>
<script src="dialog.css" type="stylesheet"></script>
<link rel="stylesheet" href="dialog.css">
</head>
<body>
<div class="container">
<div class="card mt-3">
<div class="card-header">
登録済みドメインのメールアドレス
<div id="myCardContainer" style="margin-left: 20px; margin-right: 20px;">
<fluent-card>
<h3>登録済みドメインのメールアドレス</h3>
<fluent-divider></fluent-divider>
<div id="trusted-domains">
</div>
<div class="card-body" id="trusted-domains"></div>
</div>
<div class="card mt-3">
<div class="card-header">
外部ドメインのメールアドレス
</div>
<div class="card-body" id="untrusted-domains"></div>
</div>
<div class="card mt-3 mb-3">
<div class="card-header">
添付ファイル/その他の警告
</div>
<div class="card-body" id="attachment-files"></div>
</div>
<button type="button" class="btn btn-primary" id="ok-button" onclick="onOk()" disabled>OK</button>
<button type="button" class="btn btn-light" id="cancel-button" onclick="onCancel()">Cancel</button>
</fluent-card>
<fluent-card>
<h3>外部ドメインのメールアドレス</h3>
<fluent-divider></fluent-divider>
<div id="untrusted-domains"></div>
</fluent-card>
<fluent-card>
<h3>添付ファイル/その他の警告</h3>
<fluent-divider></fluent-divider>
<div id="attachment-and-others"></div>
</fluent-card>
<fluent-button id="ok-button" appearance="accent" onclick="onOk()" disabled>OK</fluent-button>
<fluent-button id="cancel-button" onclick="onCancel()">Cancel</button>
</div>
</body>
</html>
217 changes: 149 additions & 68 deletions src/web/dialog.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,68 @@
let totalCheckboxCount = 0;
function parse(recipient) {
const address = /<([^@]+@[^>]+)>\s*$/.test(recipient) ? RegExp.$1 : recipient;
const domain = address.split('@')[1].toLowerCase();
return {
recipient,
address,
domain,
};
}

class RecipientClassifier {
constructor({ internalDomains } = {}) {
const uniquePatterns = new Set(
(internalDomains || [])
.filter(pattern => !pattern.startsWith('#')) // reject commented out items
.map(
pattern => pattern.toLowerCase()
.replace(/^(-?)@/, '$1') // delete needless "@" from domain only patterns: "@example.com" => "example.com"
.replace(/^(-?)(?![^@]+@)/, '$1*@') // normalize to full address patterns: "[email protected]" => "[email protected]", "example.com" => "*@example.com"
)
);
const negativeItems = new Set(
[...uniquePatterns]
.filter(pattern => pattern.startsWith('-'))
.map(pattern => pattern.replace(/^-/, ''))
);
for (const negativeItem of negativeItems) {
uniquePatterns.delete(negativeItem);
uniquePatterns.delete(`-${negativeItem}`);
}
this.$internalPatternsMatcher = new RegExp(`^(${[...uniquePatterns].map(pattern => this.$toRegExpSource(pattern)).join('|')})$`, 'i');
this.classify = this.classify.bind(this);
}

$toRegExpSource(source) {
// https://stackoverflow.com/questions/6300183/sanitize-string-of-regex-characters-before-regexp-build
const sanitized = source.replace(/[#-.]|[[-^]|[?|{}]/g, '\\$&');

const wildcardAccepted = sanitized.replace(/\\\*/g, '.*').replace(/\\\?/g, '.');

return wildcardAccepted;
}

classify(recipients) {
const internals = new Set();
const externals = new Set();

for (const recipient of recipients) {
const classifiedRecipient = {
...parse(recipient),
};
const address = classifiedRecipient.address;
if (this.$internalPatternsMatcher.test(address))
internals.add(classifiedRecipient);
else
externals.add(classifiedRecipient);
}

return {
internals: Array.from(internals),
externals: Array.from(externals),
};
}
}


// eslint-disable-next-line @typescript-eslint/no-unused-vars
Office.initialize = function (reason) {};
Expand All @@ -8,6 +72,11 @@ Office.onReady(function () {
sendStatusToParent("ready");
});

let counter = 0;
function generateTempId() {
return `fcm_temp_${counter++}_${Date.now()}`;
}

function sendStatusToParent(status) {
const messageObject = { status: status };
const jsonMessage = JSON.stringify(messageObject);
Expand All @@ -25,22 +94,71 @@ function onCancel() {
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
function checkboxChanged() {
const checkedCount = $(".check-target:checked").length;
const isAllBoxChecked = checkedCount === totalCheckboxCount;
$("#ok-button").prop("disabled", !isAllBoxChecked);
function checkboxChanged(target_element) {
const checkTargetLength = $("fluent-checkbox.check-target").length;
let checkedTargetLength = $("fluent-checkbox.check-target.checked").length;
// If the target is currently checked, the target is unchecked after this function and vice versa.
const is_currently_checked = $(target_element).hasClass('checked');
if (is_currently_checked)
{
checkedTargetLength -= 1;
}
else
{
checkedTargetLength += 1;
}
const hasUnchecked = checkTargetLength !== checkedTargetLength;
$("#ok-button").prop("disabled", hasUnchecked);
}

function appendCheckboxes(target, groupedRecipients)
{
for (const key in groupedRecipients)
{
const recipients = groupedRecipients[key];
const idForGroup = generateTempId();
const idForGroupTitle = generateTempId();
target.append(`
<div>
<h4 id="${idForGroupTitle}"></h4>
<fluent-stack id=${idForGroup} orientation="vertical" vertical-align="start"></fluent-stack>
</div>`);
//In order to escape special chars, adding values with the text function.
$(`#${idForGroupTitle}`).text(key);
const targetElement = $(`#${idForGroup}`)
for(const recipient of recipients){
appendCheckbox(targetElement, generateTempId(), recipient.address);
}
}
}

function appendCheckbox(target, id, value) {
target.append(`
<div class="form-check">
<input class="form-check-input check-target" type="checkbox" id="${id}" onchange="checkboxChanged()">
<label class="form-check-label" for="${id}">
</label>
</div>`);
target.append(`<fluent-checkbox id="${id}" class="check-target" onchange="checkboxChanged(this)"></fluent-checkbox>`);
//In order to escape special chars, adding values with the text function.
$('label[for="' + id + '"]').text(value);
totalCheckboxCount += 1;
$(`#${id}`).text(value);
}

function classifyRecipients({ to, cc, bcc, trustedDomains }) {
const classifier = new RecipientClassifier({
internalDomains: trustedDomains || [],
});
const classifiedTo = classifier.classify(to);
const classifiedCc = classifier.classify(cc);
const classifiedBcc = classifier.classify(bcc);
console.log('classified results: ', { classifiedTo, classifiedCc, classifiedBcc });

return {
internals: new Set([
...classifiedTo.internals.map(recipient => ({ ...recipient, type: 'To' })),
...classifiedCc.internals.map(recipient => ({ ...recipient, type: 'Cc' })),
...classifiedBcc.internals.map(recipient => ({ ...recipient, type: 'Bcc' })),
]),
externals: new Set([
...classifiedTo.externals.map(recipient => ({ ...recipient, type: 'To' })),
...classifiedCc.externals.map(recipient => ({ ...recipient, type: 'Cc' })),
...classifiedBcc.externals.map(recipient => ({ ...recipient, type: 'Bcc' })),
]),
};
}

function onMessageFromParent(arg) {
Expand All @@ -61,59 +179,22 @@ function onMessageFromParent(arg) {
// }

console.log(data);

const trustedRecipients = new Set();
const untrustedRecipients = new Set();
//const matchedAttachments = new Set();

let recipients = [];
if (data.target.to) {
recipients = recipients.concat(data.target.to.map((_) => _.emailAddress));
}
if (data.target.cc) {
recipients = recipients.concat(data.target.cc.map((_) => _.emailAddress));
}
if (data.target.bcc) {
recipients = recipients.concat(data.target.bcc.map((_) => _.emailAddress));
}

console.log(recipients);

if (data.config.trustedDomains) {
for (const recipient of recipients) {
let matched = false;
for (const trustedDomain of data.config.trustedDomains) {
matched = recipient.indexOf(trustedDomain) >= 0;
if (matched) {
break;
}
}
if (matched) {
trustedRecipients.add(recipient);
} else {
untrustedRecipients.add(recipient);
}
}
}

console.debug(trustedRecipients);
console.debug(untrustedRecipients);

if (trustedRecipients.size > 0) {
//Make id uniq in this page by adding a trailing number.
//This is a temporary implementation.
let num = 0;
for (const trustedRecipient of trustedRecipients) {
const id = `trusted-${num++}`;
appendCheckbox($("#trusted-domains"), id, trustedRecipient);
}
}

if (untrustedRecipients.size > 0) {
let num = 0;
for (const untrustedRecipient of untrustedRecipients) {
const id = `untrusted-${num++}`;
appendCheckbox($("#untrusted-domains"), id, untrustedRecipient);
}
}
const to = data.target.to ?
data.target.to.map((_) => _.emailAddress):
[];
let cc = data.target.cc ?
data.target.cc.map((_) => _.emailAddress):
[];
let bcc = data.target.cc ?
data.target.bcc.map((_) => _.emailAddress):
[];
const trustedDomains = data.config.trustedDomains;

const classifiedRecipients = classifyRecipients({to, cc, bcc, trustedDomains});
console.log(classifiedRecipients);

const groupedByTypeInternals = Object.groupBy(classifiedRecipients.internals, item => item.domain);
appendCheckboxes($("#trusted-domains"), groupedByTypeInternals);
const groupedByTypeExternals = Object.groupBy(classifiedRecipients.externals, item => item.domain);
appendCheckboxes($("#untrusted-domains"), groupedByTypeExternals);
}

0 comments on commit decfc74

Please sign in to comment.