From 09dbb250a72ee77a968ea4a826cc91adb9ae5f07 Mon Sep 17 00:00:00 2001 From: lebaudantoine Date: Wed, 10 Jun 2026 13:52:56 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=9A=B8(addon)=20switch=20add=20button=20t?= =?UTF-8?q?o=20remove=20when=20a=20link=20already=20exists?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From the task panel, change the "add" button to "remove" when a meeting link is already present. This will be updated to "update" once the external API supports updating an existing room. --- .../outlook/src/common/meetingDetector.js | 64 +++++++++- .../outlook/src/locales/de/translation.json | 2 + .../outlook/src/locales/en/translation.json | 2 + .../outlook/src/locales/fr/translation.json | 2 + src/addons/outlook/src/taskpane/taskpane.css | 23 ++++ src/addons/outlook/src/taskpane/taskpane.html | 3 + src/addons/outlook/src/taskpane/taskpane.js | 109 ++++++++++++++---- 7 files changed, 183 insertions(+), 22 deletions(-) diff --git a/src/addons/outlook/src/common/meetingDetector.js b/src/addons/outlook/src/common/meetingDetector.js index 0f3edb60..a01fb6fa 100644 --- a/src/addons/outlook/src/common/meetingDetector.js +++ b/src/addons/outlook/src/common/meetingDetector.js @@ -42,4 +42,66 @@ function _containsMeetingUrl(text) { return text.includes(BASE_URL); } -module.exports = { isMeetingAlreadyAdded }; +function removeMeetingLink(item) { + return Promise.all([_removeFromBody(item), _removeFromLocation(item)]); +} + +function _removeFromBody(item) { + return new Promise((resolve) => { + item.body.getAsync(Office.CoercionType.Html, (result) => { + if (result.status !== Office.AsyncResultStatus.Succeeded) { + resolve(); + return; + } + const cleaned = _cleanBody(result.value || ""); + if (cleaned === null) { + resolve(); + return; + } + item.body.setAsync(cleaned, { coercionType: Office.CoercionType.Html }, () => resolve()); + }); + }); +} + +function _removeFromLocation(item) { + if (item.itemType !== Office.MailboxEnums.ItemType.Appointment) { + return Promise.resolve(); + } + return new Promise((resolve) => { + item.location.getAsync((result) => { + if ( + result.status === Office.AsyncResultStatus.Succeeded && + _containsMeetingUrl(result.value) + ) { + item.location.setAsync("", () => resolve()); + } else { + resolve(); + } + }); + }); +} + +const SEPARATOR = /─{10,}/; + +/** + * Returns cleaned HTML, or null if no meeting block was found. + */ +function _cleanBody(html) { + const doc = new DOMParser().parseFromString(html, "text/html"); + + const hits = []; + const walker = doc.createTreeWalker(doc.body, NodeFilter.SHOW_TEXT); + while (walker.nextNode()) { + if (SEPARATOR.test(walker.currentNode.nodeValue)) hits.push(walker.currentNode); + } + if (hits.length < 2) return null; + + const range = doc.createRange(); + range.setStartBefore(hits[0]); + range.setEndAfter(hits[hits.length - 1]); + range.deleteContents(); + + return doc.documentElement.outerHTML; +} + +module.exports = { isMeetingAlreadyAdded, removeMeetingLink }; diff --git a/src/addons/outlook/src/locales/de/translation.json b/src/addons/outlook/src/locales/de/translation.json index 519bcabc..8de0e75f 100644 --- a/src/addons/outlook/src/locales/de/translation.json +++ b/src/addons/outlook/src/locales/de/translation.json @@ -20,6 +20,8 @@ "link_inserted": "Besprechungslink erfolgreich eingefügt", "generating": "Wird erstellt...", "add_meeting": "{{app_name}}-Besprechung hinzufügen", + "remove_meeting": "{{app_name}}-Besprechung entfernen", + "removing": "Wird entfernt...", "error": { "auth": "Ihre Sitzung ist abgelaufen. Bitte versuchen Sie es erneut.", "retry": "Es ist ein Fehler aufgetreten. Bitte versuchen Sie es erneut.", diff --git a/src/addons/outlook/src/locales/en/translation.json b/src/addons/outlook/src/locales/en/translation.json index 60082f54..b433ec03 100644 --- a/src/addons/outlook/src/locales/en/translation.json +++ b/src/addons/outlook/src/locales/en/translation.json @@ -20,6 +20,8 @@ "link_inserted": "Meeting link inserted successfully", "generating": "Generating...", "add_meeting": "Add a {{app_name}} meeting", + "remove_meeting": "Remove the {{app_name}} meeting", + "removing": "Removing...", "error": { "auth": "Your session has expired. Please try again.", "retry": "An error occurred. Please try again.", diff --git a/src/addons/outlook/src/locales/fr/translation.json b/src/addons/outlook/src/locales/fr/translation.json index b1df41f1..cdb3bb3d 100644 --- a/src/addons/outlook/src/locales/fr/translation.json +++ b/src/addons/outlook/src/locales/fr/translation.json @@ -20,6 +20,8 @@ "link_inserted": "Lien de réunion inséré avec succès", "generating": "Génération...", "add_meeting": "Ajouter une réunion {{app_name}}", + "remove_meeting": "Supprimer la réunion {{app_name}}", + "removing": "Suppression en cours...", "error": { "auth": "Connexion expirée, veuillez réessayer.", "retry": "Une erreur est survenue, veuillez ré-essayer", diff --git a/src/addons/outlook/src/taskpane/taskpane.css b/src/addons/outlook/src/taskpane/taskpane.css index 1bc686de..fa1b4243 100644 --- a/src/addons/outlook/src/taskpane/taskpane.css +++ b/src/addons/outlook/src/taskpane/taskpane.css @@ -115,6 +115,29 @@ button { background-color: #f5f5f5; } +/* ── Danger button (remove meeting) ── */ + +#btn-remove { + background-color: #CA3632; /* error.400 */ + color: #FFFFFF; + border: none; +} + +#btn-remove:hover { + background-color: #EE6A66; /* error.600 */ +} + +#btn-remove:active { + background-color: #F28D8A; /* error.700 */ + color: #F6AFAD; /* error.200 */ +} + +#btn-remove:disabled { + background-color: #F6AFAD; /* error.800 */ + color: #FAD2D1; /* error.900 */ + cursor: not-allowed; +} + /* ── Version ── */ #version-tag { diff --git a/src/addons/outlook/src/taskpane/taskpane.html b/src/addons/outlook/src/taskpane/taskpane.html index 05386b48..31909b12 100644 --- a/src/addons/outlook/src/taskpane/taskpane.html +++ b/src/addons/outlook/src/taskpane/taskpane.html @@ -41,7 +41,10 @@ diff --git a/src/addons/outlook/src/taskpane/taskpane.js b/src/addons/outlook/src/taskpane/taskpane.js index 7e1e50da..09e0e2a7 100644 --- a/src/addons/outlook/src/taskpane/taskpane.js +++ b/src/addons/outlook/src/taskpane/taskpane.js @@ -1,24 +1,77 @@ +/* global Office */ const { APP_NAME } = require("../common"); - const { applyAppName } = require("../common/helpers"); const { initSession, createRoom } = require("../common/api"); const { startPolling } = require("../common/polling"); const { openTransitDialog } = require("../common/transitDialog"); const { loadSession, saveSession, clearSession } = require("../common/session"); - const { buildMeetingMessage } = require("../common/messageBuilder"); const { initI18n, t, translateUI } = require("../common/i18n"); +const { isMeetingAlreadyAdded, removeMeetingLink } = require("../common/meetingDetector"); +// ── Views ──────────────────────────────────────────────────── -// todo - support loading view while polling -// todo - support error view function showView(name) { document.getElementById("view-loading").style.display = "none"; document.getElementById("view-unauth").style.display = "none"; document.getElementById("view-auth").style.display = "none"; document.getElementById(`view-${name}`).style.display = "block"; + + if (name === "auth") { + _refreshMeetingButtonState(); + } } +// ── Button state ───────────────────────────────────────────── + +function _showAddButton() { + document.getElementById("btn-generate").style.display = "block"; + document.getElementById("btn-remove").style.display = "none"; +} + +function _showRemoveButton() { + document.getElementById("btn-generate").style.display = "none"; + document.getElementById("btn-remove").style.display = "block"; +} + +function _setButtonLoading() { + const btn = document.getElementById("btn-generate"); + btn.disabled = true; + btn.textContent = t("meeting.generating"); +} + +function _setButtonIdle() { + const btn = document.getElementById("btn-generate"); + btn.disabled = false; + btn.textContent = t("meeting.add_meeting", { app_name: APP_NAME }); +} + +function _setRemoveLoading() { + const btn = document.getElementById("btn-remove"); + btn.disabled = true; + btn.textContent = t("meeting.removing"); +} + +function _setRemoveIdle() { + const btn = document.getElementById("btn-remove"); + btn.disabled = false; + btn.textContent = t("meeting.remove_meeting", { app_name: APP_NAME }); +} + +function _refreshMeetingButtonState() { + const item = Office.context.mailbox.item; + if (!item) return; + isMeetingAlreadyAdded(item).then((alreadyAdded) => { + if (alreadyAdded) { + _showRemoveButton(); + } else { + _showAddButton(); + } + }); +} + +// ── Auth ───────────────────────────────────────────────────── + function connect() { initSession() .then((data) => { @@ -49,24 +102,11 @@ function disconnect() { clearSession().finally(() => showView("unauth")); } -function _setButtonLoading() { - const btn = document.getElementById("btn-generate"); - btn.disabled = true; - btn.textContent = t("meeting.generating"); -} - -function _setButtonIdle() { - const btn = document.getElementById("btn-generate"); - btn.disabled = false; - btn.textContent = t("meeting.add_meeting", { - app_name: APP_NAME, - }); -} +// ── Meeting ────────────────────────────────────────────────── function generateMeetingLink() { const session = loadSession(); if (!session?.access_token) { - console.error("Session introuvable. Veuillez vous reconnecter."); showView("unauth"); return; } @@ -86,7 +126,6 @@ function generateMeetingLink() { reject(setResult.error); return; } - // ─── If calendar event, also set location ────────────── if (item.itemType === Office.MailboxEnums.ItemType.Appointment) { item.location.setAsync(url, () => resolve()); return; @@ -95,6 +134,9 @@ function generateMeetingLink() { }); }); }) + .then(() => { + _showRemoveButton(); + }) .catch((err) => { console.error(err); }) @@ -103,8 +145,32 @@ function generateMeetingLink() { }); } -Office.onReady(async (info) => { +function removeMeetingLinkFromItem() { + const session = loadSession(); + if (!session?.access_token) { + showView("unauth"); + return; + } + _setRemoveLoading(); + + const item = Office.context.mailbox.item; + + removeMeetingLink(item) + .then(() => { + _showAddButton(); + }) + .catch((err) => { + console.error(err); + }) + .finally(() => { + _setRemoveIdle(); + }); +} + +// ── Init ───────────────────────────────────────────────────── + +Office.onReady(async (info) => { await initI18n(); translateUI(); @@ -115,10 +181,11 @@ Office.onReady(async (info) => { document.getElementById("btn-connect").onclick = connect; document.getElementById("btn-disconnect").onclick = disconnect; document.getElementById("btn-generate").onclick = generateMeetingLink; + document.getElementById("btn-remove").onclick = removeMeetingLinkFromItem; const session = loadSession(); if (session?.state === "authenticated" && session?.access_token) { - showView("auth"); + showView("auth"); // this already calls _refreshMeetingButtonState internally } else { showView("unauth"); }