Skip to content
Snippets Groups Projects
Commit b7b15d75 authored by Reinhold Kainhofer's avatar Reinhold Kainhofer
Browse files

Some more work, fighting with new mail generation..

parent 84f58e7c
No related branches found
No related tags found
No related merge requests found
......@@ -16,7 +16,7 @@
<Host Name="Mailbox"/>
</Hosts>
<Requirements>
<Sets>
<Sets DefaultMinVersion="1.8">
<Set Name="Mailbox" MinVersion="1.1"/>
</Sets>
</Requirements>
......@@ -28,7 +28,7 @@
</DesktopSettings>
</Form>
</FormSettings>
<Permissions>ReadWriteItem</Permissions>
<Permissions>ReadWriteMailbox</Permissions>
<Rule xsi:type="RuleCollection" Mode="Or">
<Rule xsi:type="ItemIs" ItemType="Message" FormType="Read"/>
</Rule>
......
......@@ -15,22 +15,24 @@
<p id="messageText"></p>
</div>
<!--fieldset>
<legend>1. Select data source (Excel file):</legend>
<input type="file" id="excelFileInput" accept=".xlsx, .xlsm, .xlsb, .xlts, .xltm, .xls" />
<div class="worksheet_select">Worksheet: <select id="worksheetSelect" disabled></select></div>
</fieldset-->
<fieldset>
<legend>1. Select data source (Excel file):</legend>
<button id="selectFileButton">Select File</button>
<span id="selectedFileName" style="margin-left: 10px; font-slant: italic;">No file selected</span>
<span id="selectedFileName" style="margin-left: 10px;">No file selected</span>
<div class="worksheet_select">
Worksheet: <select id="worksheetSelect" disabled></select>
</div>
</fieldset>
<fieldset>
<legend>2. E-Mail - Placeholder fields:</legend>
<legend>2. E-Mail - Placeholders fields:</legend>
<div class="recipient_select">
<label for="recipientSelect">Recipient column:</label>
<select id="recipientSelect" disabled>
<option value="">-</option>
</select>
</div>
<label for="placeholderSelect">Placeholder:</label>
<select id="placeholderSelect" disabled></select>
<button id="placeholderInsertButton">Insert</button>
......@@ -64,7 +66,7 @@
<div id="dataPreview"></div>
<script src="https://cdn.sheetjs.com/xlsx-latest/package/dist/xlsx.full.min.js"></script>
<script src="https://appsforoffice.microsoft.com/lib/1/hosted/office.js"></script>
<script src="taskpane.js"></script>
<!-- script src="taskpane.js"></script-->
</div>
</body>
</html>
......@@ -4,9 +4,9 @@
// Ensure the Office.js library is loaded the office context is fully initialized!
Office.onReady(function (info) {
/*Office.onReady(function (info) {
// Office is ready
/* if (info.host === Office.HostType.Outlook) {
if (info.host === Office.HostType.Outlook) {
// Now you can safely use Office.context.mailbox
Office.context.mailbox.item.body.getTypeAsync((result) => {
if (result.status === Office.AsyncResultStatus.Succeeded) {
......@@ -15,8 +15,8 @@ Office.onReady(function (info) {
console.error("Failed to get body type:", result.error);
}
});
}*/
});
}
});*/
let workbook = null;
......@@ -30,9 +30,14 @@ let attachmentsFolderHandle = null;
// Create a global EventTarget instance
const eventBus = new EventTarget();
eventBus.addEventListener("setSpreadsheetFile", async (event) => { setSpreadsheetFile(event.detail.handle) });
function debugLog(message) {
const timestamp = new Date().toISOString();
console.log(`[${timestamp}] ${message}`);
}
/***
* displayMessage: Warning/Errors in a temporary, non-blocking popup div
*/
......@@ -66,6 +71,42 @@ document.getElementById("closeMessageButton").addEventListener("click", function
function populateColumnSelect(select, columns, noSelection = "") {
if (!select) {
debugLog("populateColumnSelect called with NULL select.");
return;
}
const currentSelectedValue = select.value;
if (noSelection == "") {
select.innerHTML = "";
} else {
select.innerHTML = `<option value="">${noSelection}</option>`;
}
columns.forEach((column, index) => {
const option = document.createElement("option");
option.value = column;
option.textContent = column || `Column ${index + 1}`;
select.appendChild(option);
});
// Check if the previously selected value exists in the new list
const matchingOption = Array.from(select.options).find(option =>
option.value === currentSelectedValue || option.textContent === currentSelectedValue
);
// If the selected value is found, restore it
if (matchingOption) {
select.value = currentSelectedValue;
} else if (columns.length > 0) {
// If no match is found, default to the first option
select.value = columns[0];
}
select.disabled = false;
select.dispatchEvent(new Event("change"));
}
/***********************************************************
......@@ -79,6 +120,7 @@ const worksheetSelect = document.getElementById("worksheetSelect");
worksheetSelect.addEventListener("change", handleWorksheetSelection);
document.getElementById("selectFileButton").addEventListener("click", async () => {
debugLog("called click handler of selectFileButton");
try {
// Open the file picker and allow selecting a single file
/* if (!('showOpenFilePicker' in window)) {
......@@ -100,13 +142,10 @@ document.getElementById("selectFileButton").addEventListener("click", async () =
multiple: false,
});
const setSpreadsheetEvent = new CustomEvent("setSpreadsheetFile", {
detail: {
handle: handle, // Pass the file handle
},
});
eventBus.dispatchEvent(setSpreadsheetEvent);
debugLog("After file picker");
setSpreadsheetFile(handle);
} catch (err) {
debugLog("Catch inside click handler of selectFileButton");
console.error("File selection canceled or failed", err);
}
});
......@@ -117,9 +156,9 @@ document.getElementById("selectFileButton").addEventListener("click", async () =
/*
* Read the selected file (Excel, csv) into memory
*/
eventBus.addEventListener("setSpreadsheetFile", async (event) => {
async function setSpreadsheetFile(handle) {
// Save the file handle for later access
const handle = event.detail.handle;
if (!handle) return;
const permissionStatus = await handle.queryPermission();
......@@ -152,7 +191,7 @@ eventBus.addEventListener("setSpreadsheetFile", async (event) => {
// Populate the worksheet dropdown
populateWorksheetDropdown(sheetNames);
});
};
......@@ -215,6 +254,8 @@ function handleWorksheetSelection(event) {
*
***********************************************************/
const recipientSelect = document.getElementById("recipientSelect");
const placeholderSelect = document.getElementById("placeholderSelect");
const placeholderInsertButton = document.getElementById("placeholderInsertButton");
const placeholderCopyLabel = document.getElementById("placeholderCopyLabel");
......@@ -223,35 +264,10 @@ const placeholderCopyLabel = document.getElementById("placeholderCopyLabel");
placeholderSelect.addEventListener("change", enablePlaceholderInsert);
placeholderInsertButton.addEventListener("click", insertPlaceholder);
placeholderCopyLabel.addEventListener("click", copyPlaceholderToClipboard);
eventBus.addEventListener("dataReady", populatePlaceholderSelect);
function populatePlaceholderSelect(event) {
const columns = event.detail.columns;
const currentSelectedValue = placeholderSelect.value;
placeholderSelect.innerHTML = "";
eventBus.addEventListener("dataReady", (event) => { populateColumnSelect(placeholderSelect, event.detail.columns, ""); });
eventBus.addEventListener("dataReady", (event) => { populateColumnSelect(recipientSelect, event.detail.columns, "-- (no individual recipients)"); });
columns.forEach((column, index) => {
const option = document.createElement("option");
option.value = column;
option.textContent = column || `Column ${index + 1}`;
placeholderSelect.appendChild(option);
});
// Check if the previously selected value exists in the new list
const matchingOption = Array.from(attachmentColumnSelect.options).find(option =>
option.value === currentSelectedValue || option.textContent === currentSelectedValue
);
// If the selected value is found, restore it
if (matchingOption) {
attachmentColumnSelect.value = currentSelectedValue;
} else if (columns.length > 0) {
// If no match is found, default to the first option
attachmentColumnSelect.value = columns[0];
}
placeholderSelect.disabled = false;
placeholderSelect.dispatchEvent(new Event("change"));
}
function enablePlaceholderInsert(event) {
const placeholder = event.target.value;
......@@ -261,6 +277,7 @@ function enablePlaceholderInsert(event) {
// Function to insert placeholder
async function insertPlaceholder() {
debugLog("Called insertPlaceholder");
try {
// Get the selected field from the combo box
const selectedText = placeholderSelect.options[placeholderSelect.selectedIndex].text;
......@@ -272,15 +289,19 @@ async function insertPlaceholder() {
const placeholder = `{%${selectedText}%}`;
debugLog("Before inserting ${placeholder}");
// Use the Office.js API to get the current item
Office.context.mailbox.item.body.getTypeAsync((result) => {
if (result.status === Office.AsyncResultStatus.Succeeded) {
const bodyType = result.value;
debugLog("Inside async inserting ${placeholder}");
if (bodyType === Office.MailboxEnums.BodyType.Html) {
debugLog("Inside async inserting ${placeholder} to HTML");
return Office.context.mailbox.item.body.setSelectedDataAsync(placeholder, {
coercionType: Office.CoercionType.Html,
});
} else {
debugLog("Inside async inserting ${placeholder} to Text");
return Office.context.mailbox.item.body.setSelectedDataAsync(placeholder, {
coercionType: Office.CoercionType.Text,
});
......@@ -291,6 +312,8 @@ async function insertPlaceholder() {
console.error("Error inserting placeholder:", error);
displayMessage("There was an error inserting the placeholder. Please try again.");
}
debugLog("End of insertPlaceholder");
}
......@@ -356,40 +379,9 @@ const fileExtensionInput = document.getElementById("fileExtensionInput");
const attachmentFolderButton = document.getElementById("selectAttachmentFolderButton");
const attachmentFolderSpan = document.getElementById("selectedAttachmentFolder");
eventBus.addEventListener("dataReady", populateAttachmentColumnSelect);
eventBus.addEventListener("dataReady", (event) => { populateColumnSelect(attachmentColumnSelect, event.detail.columns, "No attachment (select column if needed)"); });
attachmentColumnSelect.addEventListener("change", handleAttachmentColumnSelection);
function populateAttachmentColumnSelect(event) {
// (Re-)Populate the column selection combobox. -> Store the current selection and reinstate it after re-filling the list!
const columns = event.detail.columns;
// Store the currently selected value
const currentSelectedValue = attachmentColumnSelect.value;
attachmentColumnSelect.innerHTML = '<option value="">No attachment (select column if needed)</option>';
columns.forEach((column, index) => {
const option = document.createElement("option");
option.value = column;
option.textContent = column || `Column ${index + 1}`;
attachmentColumnSelect.appendChild(option);
});
// Check if the previously selected value exists in the new list
const matchingOption = Array.from(attachmentColumnSelect.options).find(option =>
option.value === currentSelectedValue || option.textContent === currentSelectedValue
);
// If the selected value is found, restore it
if (matchingOption) {
attachmentColumnSelect.value = currentSelectedValue;
} else if (columns.length > 0) {
// If no match is found, default to the first option
attachmentColumnSelect.value = columns[0];
}
attachmentColumnSelect.disabled = false;
attachmentColumnSelect.dispatchEvent(new Event("change"));
}
appendExtensionCheckbox.addEventListener("change", () => {
fileExtensionInput.disabled = !appendExtensionCheckbox.checked;
});
......@@ -584,57 +576,79 @@ async function sendSerialMails(sendNow = false) {
const item = Office.context.mailbox.item;
const attachmentColumn = attachmentColumnSelect.options[attachmentColumnSelect.selectedIndex].text;
const hasAttachmentsConfigured = attachmentColumn && attachmentsFolderHandle;
const recipient = recipientSelect.value || ""; // Explicit column selected for recipient addresses
let fileExtension = appendExtensionCheckbox.checked ? fileExtensionInput.value : "";
if (fileExtension && !fileExtension.startsWith(".")) {
fileExtension = `.${fileExtension}`;
}
// Function to read out and return the mail properties that need getAsync...
function readMailPropertyAsync(property) {
if (!property)
return;
return new Promise((resolve, reject) => {
property.getAsync(function(result) {
if (result.status === Office.AsyncResultStatus.Succeeded) {
resolve(result.value); // Resolve with the property value (subject, cc, etc.)
} else {
reject("Failed to get property: " + result.error.message);
}
});
});
}
function readMailPropertyOptionsAsync(property, options) {
return new Promise((resolve, reject) => {
property.getAsync(options, function(result) {
if (result.status === Office.AsyncResultStatus.Succeeded) {
resolve(result.value); // Resolve with the property value (subject, cc, etc.)
} else {
reject("Failed to get property: " + result.error.message);
}
});
});
}
for (const row of worksheetData) {
try {
// Function to get the recipient list as a string (if available)
async function getRecipientAsync(recipientField) {
return new Promise((resolve, reject) => {
recipientField.getAsync((result) => {
if (result.status === Office.AsyncResultStatus.Succeeded) {
// Extract the email addresses (or return empty string if none)
const emails = result.value.map((recipient) => recipient.emailAddress).join(", ");
resolve(emails); // Join multiple recipients with a comma
} else {
reject("Error retrieving recipient");
}
});
});
// recipients (TO, CC, BCC must use fully valid email-Adresses, so we cannot use
// placeholders there -> use the recipientSelect combobox to define a table
// column for individualized recipients per record!)
// Fetch properties and replace placeholders
const to = await readMailPropertyAsync(item.to);
const cc = await readMailPropertyAsync(item.cc);
const bcc = await readMailPropertyAsync(item.bcc);
// const from = await readMailPropertyAsync(item.from);
const attachments = await readMailPropertyAsync(item.attachments);
const subject = await readMailPropertyAsync(item.subject);
const body = await readMailPropertyOptionsAsync(item.body, Office.CoercionType.Html);
const new_subject = replacePlaceholders(subject, row);
const new_body = replacePlaceholders(body, row);
if (recipient != "") { // add inidividualized recipient
const new_recipient = replacePlaceholders(`{%${recipient}%}`, row);
if (new_recipient != "") {
to.push(new_recipient);
}
}
// Fetch recipients asynchronously and replace placeholders
const to = await getRecipientAsync(item.to);
const cc = await getRecipientAsync(item.cc);
const bcc = await getRecipientAsync(item.bcc);
const subject = replacePlaceholders(String(item.subject), row);
// Create a copy of the email
const mailItem = await Office.context.mailbox.displayNewMessageForm({
subject: new_subject,
htmlBody: new_body,
toRecipients: to,
ccRecipients: cc,
bccRecipients: bcc,
attachments: attachments,
});
// Setting the sender from the original message is not possible. Outlook will
// always use the one it determines (should be the one selected for the currently
// selected message, but there is no guarantee. It might also be the primary account.)
// Replace fields
// mailItem.subject.setAsync(new_subject, { coercionType: Office.CoercionType.Html });
// mailItem.body.setAsync(new_body, { coercionType: Office.CoercionType.Html });
const bodyResult = await item.body.getAsync(Office.CoercionType.Html);
const body = replacePlaceholders(bodyResult.value || "", row);
// Create a copy of the email
const mailItem = await Office.context.mailbox.item.displayReplyAllFormAsync();
// Replace fields
const new_to = replacePlaceholders(to, row);
const new_cc = replacePlaceholders(cc, row);
const new_bcc = replacePlaceholders(bcc, row);
const new_subject = replacePlaceholders(item.subject, row);
if (to != new_to)
mailItem.to = new_to;
if (cc != new_cc)
mailItem.cc = new_cc;
if (bcc != new_bcc)
mailItem.bcc = new_bcc;
if (subject != new_subject)
mailItem.subject = new_subject;
mailItem.body.setAsync(body, { coercionType: Office.CoercionType.Html });
// Handle attachment only if configuration and data are available
if (hasAttachmentsConfigured && attachmentColumn) {
......@@ -681,7 +695,7 @@ async function sendSerialMails(sendNow = false) {
*
*
***********************************************************/
/*
worksheetSelect.addEventListener("change", saveFormDataToRoaming);
placeholderSelect.addEventListener("change", saveFormDataToRoaming);
attachmentColumnSelect.addEventListener("change", saveFormDataToRoaming);
......@@ -736,21 +750,13 @@ async function openIndexedDB() {
};
});
}
*/
// Save file/directory handles persistently
async function saveHandles() {
// Storing/loading handles to/from Office Storage API does not work:
/* const settings = {
excelFileHandle: excelFileHandle ? await excelFileHandle.queryPermission() : null,
attachmentsFolderHandle: attachmentsFolderHandle ? await attachmentsFolderHandle.queryPermission() : null,
};
Office.context.roamingSettings.set("fileHandles", settings);
Office.context.roamingSettings.saveAsync();
*/
// Storing/loading handles to/from Office Storage API does not work.
// Instead, try storing it to indexDB instead:
const db = await openIndexedDB();
/* const db = await openIndexedDB();
if (db) {
const transaction = db.transaction(['fileHandles'], 'readwrite');
const store = transaction.objectStore('fileHandles');
......@@ -761,20 +767,18 @@ async function saveHandles() {
};
store.put(data, 'fileHandles'); // Storing the metadata
await transaction.complete;
}
}*/
}
/*
// Load file/directory handles persistently
async function loadHandles() {
// const settings = Office.context.roamingSettings.get("fileHandles"); // Storing/loading handles to/from Office Storage API does not work!
// Storing/loading handles to/from Office Storage API does not work!
const settings = await loadFromIndexedDB('fileHandles'); // Instead, try loading them from indexDB instead:
if (settings) {
if (settings.excelFileHandle) {
const setSpreadsheetEvent = new CustomEvent("setSpreadsheetFile", {
detail: { handle: settings.excelFileHandle, },
});
eventBus.dispatchEvent(setSpreadsheetEvent);
setSpreadsheetFile(settings.excelFileHandle);
}
if (settings.attachmentsFolderHandle) {
......@@ -796,17 +800,20 @@ async function loadFromIndexedDB(storeName) {
const result = await request.onsuccess;
return result.target.result;
}
*/
Office.onReady(function (info) {
if (info.host === Office.HostType.Outlook) {
/*loadHandles().then(() => {
console.log("Office.onReady");
/* if (info.host === Office.HostType.Outlook) {
loadHandles().then(() => {
if (excelFileHandle) {
console.log("Excel file handle loaded successfully");
}
if (attachmentsFolderHandle) {
console.log("Attachment folder handle loaded successfully");
}
});*/
// loadFormDataFromRoaming();
});
loadFormDataFromRoaming();
}
*/
});
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment