From 5d6aed3603facdebf2622d858cd39dddfe824ed1 Mon Sep 17 00:00:00 2001 From: devpotatoes Date: Mon, 25 Aug 2025 17:47:12 +0200 Subject: [PATCH] SPA privatri format --- wco/itm/privatri.json | 11 +- wco/privatri/alias.mustache | 6 + wco/privatri/createThread.mustache | 34 + wco/privatri/editMessage.mustache | 16 + wco/privatri/inviteAlias.mustache | 12 + wco/privatri/main.mustache | 151 ++-- wco/privatri/message.mustache | 23 + wco/privatri/privatri.js | 771 +++++++++++++++++- wco/privatri/thread.mustache | 7 + wco/privatri/threadAliasList.mustache | 6 + wco/privatri/threadSettings.mustache | 33 + wco/privatri/toastAlert.mustache | 5 + .../static/lib/dayjs/plugin/arraySupport.d.ts | 0 .../lib/dayjs/plugin/objectSupport.d.ts | 0 .../src/tpldata/privatri_main_privatri.json | 29 +- .../static/lib/dayjs/plugin/arraySupport.d.ts | 0 .../lib/dayjs/plugin/objectSupport.d.ts | 0 wwws/itm/admin.json | 15 +- 18 files changed, 1009 insertions(+), 110 deletions(-) create mode 100644 wco/privatri/alias.mustache create mode 100644 wco/privatri/createThread.mustache create mode 100644 wco/privatri/editMessage.mustache create mode 100644 wco/privatri/inviteAlias.mustache create mode 100644 wco/privatri/message.mustache create mode 100644 wco/privatri/thread.mustache create mode 100644 wco/privatri/threadAliasList.mustache create mode 100644 wco/privatri/threadSettings.mustache create mode 100644 wco/privatri/toastAlert.mustache mode change 100755 => 100644 wwws/admin/src/static/lib/dayjs/plugin/arraySupport.d.ts mode change 100755 => 100644 wwws/admin/src/static/lib/dayjs/plugin/objectSupport.d.ts mode change 100755 => 100644 wwws/apx/src/static/lib/dayjs/plugin/arraySupport.d.ts mode change 100755 => 100644 wwws/apx/src/static/lib/dayjs/plugin/objectSupport.d.ts diff --git a/wco/itm/privatri.json b/wco/itm/privatri.json index 33d1656..2b25f79 100644 --- a/wco/itm/privatri.json +++ b/wco/itm/privatri.json @@ -9,7 +9,16 @@ "description": "Composant pour gérer les accès et les données privées.", "lang": ["fr"], "tpl": { - "privatrimain": "apxtri/objects/wco/privatri/main.mustache" + "privatrimain": "apxtri/objects/wco/privatri/main.mustache", + "privatriAlias": "apxtri/objects/wco/privatri/alias.mustache", + "privatriCreateThread": "apxtri/objects/wco/privatri/createThread.mustache", + "privatriEditMessage": "apxtri/objects/wco/privatri/editMessage.mustache", + "privatriInviteAlias": "apxtri/objects/wco/privatri/inviteAlias.mustache", + "privatriMessage": "apxtri/objects/wco/privatri/message.mustache", + "privatriThread": "apxtri/objects/wco/privatri/thread.mustache", + "privatriThreadAliasList": "apxtri/objects/wco/privatri/threadAliasList.mustache", + "privatriThreadSettings": "apxtri/objects/wco/privatri/threadSettings.mustache", + "privatriToastAlert": "apxtri/objects/wco/privatri/toastAlert.mustache" }, "tpldatamodel": { "privatri": "apxtri/objects/wco/privatri/exampleprivatri" }, "schema": [], diff --git a/wco/privatri/alias.mustache b/wco/privatri/alias.mustache new file mode 100644 index 0000000..bfabfaa --- /dev/null +++ b/wco/privatri/alias.mustache @@ -0,0 +1,6 @@ + \ No newline at end of file diff --git a/wco/privatri/createThread.mustache b/wco/privatri/createThread.mustache new file mode 100644 index 0000000..cfbf198 --- /dev/null +++ b/wco/privatri/createThread.mustache @@ -0,0 +1,34 @@ +
+

New thread

+
+ + + + +
+ + +
+ +

Urgency deletion

+
+ +
\ No newline at end of file diff --git a/wco/privatri/editMessage.mustache b/wco/privatri/editMessage.mustache new file mode 100644 index 0000000..6358d23 --- /dev/null +++ b/wco/privatri/editMessage.mustache @@ -0,0 +1,16 @@ + +
+ {{date}} +
+ + +
+
\ No newline at end of file diff --git a/wco/privatri/inviteAlias.mustache b/wco/privatri/inviteAlias.mustache new file mode 100644 index 0000000..ce01b6d --- /dev/null +++ b/wco/privatri/inviteAlias.mustache @@ -0,0 +1,12 @@ +
+

Invite an alias into this thread

+
+
+ + +
+
\ No newline at end of file diff --git a/wco/privatri/main.mustache b/wco/privatri/main.mustache index b4e0423..8ba0284 100644 --- a/wco/privatri/main.mustache +++ b/wco/privatri/main.mustache @@ -1,88 +1,85 @@
-
- -
-
-
-
- {threadName} -
-
+
+
+
+ {threadName} +
+ +
+
+
+ +
+ + -
- - -
+
\ No newline at end of file diff --git a/wco/privatri/message.mustache b/wco/privatri/message.mustache new file mode 100644 index 0000000..9aa563f --- /dev/null +++ b/wco/privatri/message.mustache @@ -0,0 +1,23 @@ +
+
+
+ {{message}} +
+
+
+
+ {{date}} + +
+
\ No newline at end of file diff --git a/wco/privatri/privatri.js b/wco/privatri/privatri.js index a7baf37..b001e84 100644 --- a/wco/privatri/privatri.js +++ b/wco/privatri/privatri.js @@ -1,25 +1,754 @@ var apx = apx || {}; + apx.privatri = {}; -apx.privatri.loadwco = async (id, ctx) => { - // check if not authenticate, do nothing cause by default screensignin and wait authentification - // if authenticate, if url xhash then redirect if no url then change wco-link=screenmyworld - // if (dayjs(apx.data.headers.xdays).diff(dayjs(), "hours") >= 24) apx.apxauth.checkisauth(); - //load main.mustache of the component - //when wco-xxx change it run this function - console.log( - `Load wconame:privatri apx.privatri.loadwco with id:${id} and ctx: ${JSON.stringify( - ctx - )}` - ); - //refresh thread from BE - const threadlist = apx.privatri.getthread(apx.data.headers.xalias,1000000) - const tpldataname = `${apx.data.pagename}_${id}_privatri`; - const privatriid = document.getElementById(id); - privatriid.innerHTML = Mustache.render(apx.data.tpl.privatrimain, threadlist); +apx.privatri.templates = {}; -} +apx.privatri.loadwco = async (id, dataObj = {}, ctx = null) => { + // check if not authenticate, do nothing cause by default screensignin and wait authentification + // if authenticate, if url xhash then redirect if no url then change wco-link=screenmyworld + // if (dayjs(apx.data.headers.xdays).diff(dayjs(), "hours") >= 24) apx.apxauth.checkisauth(); + //load main.mustache of the component + //when wco-xxx change it run this function + console.log( + `Load wconame:privatri apx.privatri.loadwco with id:${id} and ctx: ${JSON.stringify( + ctx + )}` + ); + + const template = JSON.parse(localStorage.getItem("admin")).tpl[`privatri${id}`]; + + return Mustache.render(template, dataObj); +}; -apx.privatri.getthread=async(alias,timestamp)=>{ - //store in indexdb the thread - return true -} \ No newline at end of file +apx.privatri.syncronizeBackend = async (alias, lastConnection) => { + // const response = await fetch + const response = []; + + for (const message of response) { + await apx.indexedDB.set("privatri", "messages", message); + }; +}; + +apx.privatri.templates.scripts = { + createThread: async () => { + const autoDeletionBtnElArray = document.querySelectorAll("li.autoDeletionBtn"); + + autoDeletionBtnElArray.forEach(btn => { + btn.addEventListener("click", () => { + autoDeletionBtnElArray.forEach(btn => btn.classList.remove("bg-base-200")); + btn.classList.add("bg-base-200"); + }); + }); + + document.querySelector("#createThreadBtn").addEventListener("click", async () => { + const { publicKey, privateKey } = await apx.crypto.genKey(); + + const messageObj = await (async (publicKey) => { + const uuid = crypto.randomUUID(); + const timestamp = Date.now(); + const alias = await apx.crypto.encryptMessage(JSON.parse(localStorage.getItem("apx")).data.headers.xalias, publicKey); + + return { + privatriid: `${uuid}_${timestamp}`, + thread: uuid, + timestamp: timestamp, + owner: alias, + title: await apx.crypto.encryptMessage(document.querySelector("#threadNameInput").value, publicKey), + sender_alias: alias, + publicKey: publicKey, + aliases: [ + alias + ], + message: await apx.crypto.encryptMessage(document.querySelector("#threadDescriptionInput").value, publicKey), + dt_autodestruction: (() => { + const selectedBtn = Array.from(autoDeletionBtnElArray).find(btn => btn.classList.contains("bg-base-200")); + return parseInt(selectedBtn ? selectedBtn.getAttribute("data-auto-deletion") : 0, 10); + })(), + urgencydeletion: document.querySelector('input[type="checkbox"].toggle').checked + }; + })(publicKey); + + // Faire un post sur l'endpoint /privatri + + await apx.indexedDB.set("privatri", "threads", { uuid: messageObj.thread, privateKey: privateKey }); + await apx.indexedDB.set("privatri", "messages", messageObj); + }); + }, + inviteAlias: async () => { + const url = "https://www.facebook.com/"; + + const bodyEl = document.querySelector("body"); + const qrCodeCanvasEl = document.querySelector("#qrCodeCanvas"); + const invitationLinkInputEl = document.querySelector("#invitationLinkInput"); + + const displayToastAlert = async (message) => { + return await apx.privatri.loadwco("toastAlert", { message }); + }; + + const qrCode = new QRCodeStyling({ + width: 425, + height: 425, + type: "svg", + data: url, + image: "./assets/icon.png", + dotsOptions: { + color: "#ffffff", + type: "rounded" + }, + backgroundOptions: { + color: getComputedStyle(bodyEl).backgroundColor || "#000000", + }, + imageOptions: { + crossOrigin: "anonymous", + margin: 20 + } + }); + + qrCode.append(qrCodeCanvasEl); + + invitationLinkInputEl.value = url; + + copyBtn.addEventListener("click", async () => { + navigator.clipboard.writeText(invitationLinkInputEl.value); + + bodyEl.insertAdjacentHTML("beforeend", await displayToastAlert("Invitation link copied to clipboard.")); + + setTimeout(() => { + bodyEl.lastElementChild.remove(); + }, 3000); + }); + }, + threadAliasList: async () => { + const bodyEl = document.querySelector("body"); + const aliasListContainerEl = document.querySelector("#aliasListContainer"); + + let aliasesArray = []; + + const newAlias = async (alias) => { + return await apx.privatri.loadwco("alias", { alias }); + }; + + const displayToastAlert = async (message) => { + return await apx.privatri.loadwco("toastAlert", { message }); + }; + + const params = new URLSearchParams(window.location.search); + const uuid = params.get("uuid"); + + const privateKey = (await apx.indexedDB.get("privatri", "threads", uuid)).privateKey; + + const privatriidArray = await getOldestPrivatriids("privatri", "messages"); + + privatriidArray.forEach(async privatriid => { + if (privatriid.split("_")[0] === uuid) { + aliasesArray = (await apx.indexedDB.get("privatri", "messages", privatriid)).aliases; + const ownerAlias = (await apx.indexedDB.get("privatri", "messages", privatriid)).owner; + + for (const alias of aliasesArray) { + aliasListContainerEl.insertAdjacentHTML("beforeend", await newAlias({ + decrypted: (await apx.crypto.decryptMessage(alias, privateKey)).data, + crypted: alias + })); + }; + + document.querySelectorAll("button.removeAliasBtn").forEach(btn => { + btn.addEventListener("click", async () => { + if (JSON.parse(localStorage.getItem("apx")).data.headers.xalias === (await apx.crypto.decryptMessage(ownerAlias, privateKey)).data) { + if (btn.getAttribute("data-alias") !== ownerAlias) { + const alias = btn.getAttribute("data-alias"); + aliasesArray = aliasesArray.filter(a => a !== alias); + btn.parentElement.parentElement.remove(); + + privatriidArray.forEach(async privatriid => { + if (privatriid.split("_")[0] === uuid) { + const messageObj = await apx.indexedDB.get("privatri", "messages", privatriid); + messageObj.aliases = aliasesArray; + + await apx.indexedDB.set("privatri", "messages", messageObj); + }; + }); + } else { + bodyEl.insertAdjacentHTML("beforeend", await displayToastAlert("You cannot remove the owner of the thread.")); + + setTimeout(() => { + bodyEl.lastElementChild.remove(); + }, 3000); + }; + } else { + bodyEl.insertAdjacentHTML("beforeend", await displayToastAlert("You don't have the permissions to remove this alias.")); + + setTimeout(() => { + bodyEl.lastElementChild.remove(); + }, 3000); + }; + }); + }); + }; + }); + }, + threadSettings: async () => { + const bodyEl = document.querySelector("body"); + const threadNameInputEl = document.querySelector("#threadNameInput"); + const autoDeletionBtnElArray = document.querySelectorAll("li.autoDeletionBtn"); + const applyModificationsBtnEl = document.querySelector("#applyModificationsBtn"); + + const displayToastAlert = async (message) => { + return await apx.privatri.loadwco("toastAlert", { message }); + }; + + let messageObj = {}; + + const params = new URLSearchParams(window.location.search); + const uuid = params.get("uuid"); + + const privateKey = (await apx.indexedDB.get("privatri", "threads", uuid)).privateKey; + + const privatriidArray = await getOldestPrivatriids("privatri", "messages"); + let ownerAlias = ""; + + privatriidArray.forEach(async privatriid => { + if (privatriid.split("_")[0] === uuid) { + messageObj = await apx.indexedDB.get("privatri", "messages", privatriid); + ownerAlias = messageObj.owner; + + threadNameInputEl.value = (await apx.crypto.decryptMessage(messageObj.title, privateKey)).data; + + (Array.from(autoDeletionBtnElArray).find(el => el.getAttribute("data-auto-deletion") === String(messageObj.dt_autodestruction))).classList.add("bg-base-200"); + + if (messageObj.urgencydeletion === true) { + document.querySelector('input[type="checkbox"].toggle').checked = true; + }; + }; + }); + + applyModificationsBtnEl.addEventListener("click", async () => { + if (JSON.parse(localStorage.getItem("apx")).data.headers.xalias === (await apx.crypto.decryptMessage(ownerAlias, privateKey)).data) { + messageObj.title = await apx.crypto.encryptMessage(threadNameInputEl.value, privateKey); + messageObj.dt_autodestruction = (() => { + const selectedBtn = Array.from(autoDeletionBtnElArray).find(btn => btn.classList.contains("bg-base-200")); + return parseInt(selectedBtn ? selectedBtn.getAttribute("data-auto-deletion") : 0, 10); + })(); + messageObj.urgencydeletion = document.querySelector('input[type="checkbox"].toggle').checked; + + await apx.indexedDB.set("privatri", "messages", messageObj); + } else { + bodyEl.insertAdjacentHTML("beforeend", await displayToastAlert("You don't have the permissions to edit this thread.")); + + setTimeout(() => { + bodyEl.lastElementChild.remove(); + }, 3000); + }; + }); + + autoDeletionBtnElArray.forEach(btn => { + btn.addEventListener("click", () => { + autoDeletionBtnElArray.forEach(btn => btn.classList.remove("bg-base-200")); + btn.classList.add("bg-base-200"); + }); + }); + } +}; + +async function getOldestPrivatriids(dbName, storeName) { + return new Promise((resolve, reject) => { + const request = indexedDB.open(dbName, 1); + + request.onsuccess = (event) => { + const db = event.target.result; + + const transaction = db.transaction(storeName, "readonly"); + const store = transaction.objectStore(storeName); + const cursorRequest = store.openCursor(); + const uuidMap = {}; + + cursorRequest.onsuccess = (event) => { + const cursor = event.target.result; + if (cursor) { + const obj = cursor.value; + + const [uuid, timestamp] = obj.privatriid.split("_"); + + if (!uuidMap[uuid] || Number(timestamp) < uuidMap[uuid].timestamp) { + uuidMap[uuid] = { privatriid: obj.privatriid, timestamp: Number(timestamp) }; + }; + + cursor.continue(); + } else { + const result = Object.values(uuidMap).map(event => event.privatriid); + + resolve(result); + }; + }; + + cursorRequest.onerror = (event) => reject(event); + }; + + request.onerror = (event) => reject(event); + }); +}; + +apx.ready(async () => { + await (async () => { + console.log("ok") + document.querySelector("body").innerHTML = await apx.privatri.loadwco("main"); + + const lastConnection = JSON.parse(localStorage.getItem("lastConnection")) || Date.now(); + + await apx.privatri.syncronizeBackend(apx.data.headers.xalias, lastConnection); + + const privatriidArray = await getOldestPrivatriids("privatri", "messages"); + + const thread = async (name, uuid) => { + return await apx.privatri.loadwco("threadAliasList", { uuid, name }); + }; + + for (const privatriid of privatriidArray) { + const obj = await apx.indexedDB.get("privatri", "messages", privatriid) + + const privateKey = (await apx.indexedDB.get("privatri", "threads", obj.thread)).privateKey; + const name = (await apx.crypto.decryptMessage(obj.title, privateKey)).data; + + threadsContainerEl.insertAdjacentHTML("beforeend", await thread(name, obj.thread)); + }; + + Array.from(threadsContainerEl.children).forEach(child => { + child.addEventListener("click", async () => { + const uuid = child.getAttribute("data-uuid"); + + const messagesObj = await new Promise((resolve, reject) => { + const request = indexedDB.open("privatri", 1); + + request.onsuccess = (event) => { + const db = event.target.result; + + const transaction = db.transaction("messages", "readonly"); + const store = transaction.objectStore("messages"); + const cursorRequest = store.openCursor(); + + const result = []; + cursorRequest.onsuccess = (event) => { + const cursor = event.target.result; + + if (cursor) { + const obj = cursor.value; + const keyUuid = obj.privatriid.split("_")[0]; + + if (keyUuid === uuid) { + result.push(obj); + }; + cursor.continue(); + } else { + resolve(result); + }; + }; + + cursorRequest.onerror = (event) => reject(event); + }; + + request.onerror = (event) => reject(event); + }); + + threadPageEl.setAttribute("data-uuid", uuid); + threadPageEl.querySelector("#threadName").innerText = (await apx.crypto.decryptMessage(messagesObj[0].title, (await apx.indexedDB.get("privatri", "threads", uuid)).privateKey)).data; + + messagesContainerEl.innerHTML = ""; + + let privateKey = ""; + + for (const message of messagesObj) { + if (privateKey === "") { + privateKey = (await apx.indexedDB.get("privatri", "threads", message.thread)).privateKey; + }; + + const decryptedMessage = (await apx.crypto.decryptMessage(message.message, privateKey)).data; + + let decryptedAttachments = []; + + if (message.attachments !== undefined) { + decryptedAttachments = await (async () => { + const attachmentsArray = []; + + if (message.attachments.length > 0) { + for (const attachment of message.attachments) { + attachmentsArray.push({ + fileType: attachment.fileType, + filename: attachment.filename, + content: (await apx.crypto.decryptMessage(attachment.content, privateKey)).data + }); + }; + }; + + return attachmentsArray; + })(); + }; + + messagesContainerEl.insertAdjacentHTML("beforeend", await sendNewMessage(message.timestamp, decryptedMessage, decryptedAttachments)); + }; + }); + }); + + document.querySelectorAll("a").forEach(link => { + link.addEventListener("click", async (event) => { + event.preventDefault(); + + window.history.replaceState({}, document.title, window.location.pathname); + + const templateName = link.getAttribute("data-template"); + + if (templateName === "threadAliasList" || templateName === "threadSettings") { + window.history.pushState({}, "", `${window.location.pathname}?uuid=${threadPageEl.getAttribute("data-uuid")}`); + }; + + bodyEl.firstElementChild.innerHTML = await apx.privatri.loadwco(templateName); + + await apx.privatri.templates.scripts[templateName](); + }); + }); + })(); + + const bodyEl = document.querySelector("body"); + const searchInputEl = document.querySelector("#threadSearchBar"); + const threadFilterOptionsElArray = document.querySelector("#threadFilterOptions").querySelectorAll("li"); + const threadsContainerEl = document.querySelector("#threadsContainer"); + const threadPageEl = document.querySelector("#threadPage"); + const messageInputEl = document.getElementById("messageInput"); + const messagesContainerEl = document.querySelector("#messagesContainer"); + const attachmentsInputEl = document.querySelector("#attachmentsInput"); + + function formatDate(timestamp) { + const date = new Date(timestamp); + const day = String(date.getDate()).padStart(2, "0"); + const month = String(date.getMonth() + 1).padStart(2, "0"); + const year = date.getFullYear(); + const hours = String(date.getHours()).padStart(2, "0"); + const minutes = String(date.getMinutes()).padStart(2, "0"); + + return `${day}/${month}/${year} ${hours}:${minutes}`; + }; + + searchInputEl.addEventListener("input", () => { + const value = searchInputEl.value.toLowerCase(); + + Array.from(threadsContainerEl.children).forEach(child => { + if (child.querySelector("li > div").textContent.toLowerCase().includes(value)) { + child.style.display = "block"; + } else { + child.style.display = "none"; + }; + }); + }); + + threadFilterOptionsElArray.forEach(option => { + option.addEventListener("click", async () => { + const filter = option.getAttribute("data-filter"); + + if (filter === "date") { + Array.from(threadsContainerEl.children).sort((a, b) => { + return a.querySelector("li > span").getAttribute("data-timestamp") - b.querySelector("li > span").getAttribute("data-timestamp"); + }).forEach(child => { + threadsContainerEl.appendChild(child); + }); + } else if (filter === "name") { + Array.from(threadsContainerEl.children).sort((a, b) => { + return a.querySelector("li > div").textContent.localeCompare(b.querySelector("li > div").textContent); + }).forEach(child => { + threadsContainerEl.appendChild(child); + }); + }; + }); + }); + + const sendNewMessage = async (timestamp, message, attachmentsArray) => { + const messageTemplate = await apx.privatri.loadwco("message", { timestamp, message, date: formatDate(timestamp) }); + + const tempNode = document.createElement("div"); + tempNode.innerHTML = messageTemplate; + const messageNode = tempNode.firstElementChild; + + attachmentsArray.forEach(attachment => { + messageNode.querySelector("div.attachmentsContainer").insertAdjacentHTML("beforeend", `${attachment.filename}`); + }); + + return messageNode.outerHTML; + }; + + const editMessage = async (timestamp, message) => { + return await apx.privatri.loadwco("editMessage", { message, date: formatDate(timestamp) }); + }; + + const displayToastAlert = async (message) => { + return await apx.privatri.loadwco("toastAlert", { message }); + }; + + sendMessageBtn.addEventListener("click", async () => { + const message = messageInputEl.value.trim(); + + if (message !== "") { + const messageObj = await (async (publicKey) => { + const timestamp = Date.now(); + const alias = await apx.crypto.encryptMessage(JSON.parse(localStorage.getItem("apx")).data.headers.xalias, publicKey); + + return { + privatriid: `${threadPageEl.getAttribute("data-uuid")}_${timestamp}`, + timestamp: timestamp, + sender_alias: alias, + message: await apx.crypto.encryptMessage(message, publicKey), + attachments: await (async () => { + const attachmentsArray = []; + const localAttachmentsArray = JSON.parse(sessionStorage.getItem(`attachmentsArray_${threadPageEl.getAttribute("data-uuid")}`)) || []; + + if (localAttachmentsArray.length > 0) { + for (const attachment of localAttachmentsArray) { + attachmentsArray.push( + { + fileType: attachment.fileType, + filename: attachment.filename, + content: await apx.crypto.encryptMessage(attachment.content, publicKey) + } + ); + }; + }; + + return attachmentsArray; + })() + }; + })((await apx.indexedDB.get("privatri", "messages", `${threadPageEl.getAttribute("data-uuid")}_${messagesContainerEl.firstElementChild.getAttribute("data-timestamp")}`)).publicKey); + + const newMessageHTML = await sendNewMessage(messageObj.timestamp, message, JSON.parse(sessionStorage.getItem(`attachmentsArray_${threadPageEl.getAttribute("data-uuid")}`)) || []); + + sessionStorage.removeItem(`attachmentsArray_${threadPageEl.getAttribute("data-uuid")}`); + + messagesContainerEl.insertAdjacentHTML("beforeend", newMessageHTML); + + messageInputEl.value = ""; + + // Faire un post sur l'endpoint /privatri + + await apx.indexedDB.set("privatri", "messages", messageObj); + }; + }); + + window.addEventListener("beforeunload", () => { + // if (apx) + localStorage.setItem("lastConnection", JSON.stringify(Date.now())); + }); + + window.attachDeleteMessageEvent = async function attachDeleteMessageEvent(btn) { + const messageEl = btn.parentElement.parentElement.parentElement; + + const privateKey = (await apx.indexedDB.get("privatri", "threads", threadPageEl.getAttribute("data-uuid"))).privateKey; + + const signatureMessage = `${JSON.parse(localStorage.getItem("apx")).data.headers.xalias}_${threadPageEl.getAttribute("data-uuid")}_${messageEl.getAttribute("data-timestamp")}`; + const signature = await apx.crypto.sign(signatureMessage, privateKey); + + let verified = false; + + try { + verified = await fetch("", { + method: "GET", + body: JSON.stringify({ + message: signatureMessage, + signature: signature, + uuid: threadPageEl.getAttribute("data-uuid"), + timestamp: messageEl.getAttribute("data-timestamp"), + }) + }); + } catch (error) { + console.error("Error while verifying signature:", error); + }; + + const authorAlias = await apx.crypto.decryptMessage((await apx.indexedDB.get("privatri", "messages", `${threadPageEl.getAttribute("data-uuid")}_${messageEl.getAttribute("data-timestamp")}`)).sender_alias, privateKey).data; + + if ((JSON.parse(localStorage.getItem("apx")).data.headers.xalias === authorAlias) && (verified === true)) { + await apx.indexedDB.del("privatri", "messages", `${threadPageEl.getAttribute("data-uuid")}_${messageEl.getAttribute("data-timestamp")}`); + + messageEl.remove(); + } else { + bodyEl.insertAdjacentHTML("beforeend", await displayToastAlert("You don't have the permissions to delete this message.")); + + setTimeout(() => { + bodyEl.lastElementChild.remove(); + }, 3000); + }; + }; + + window.attachEditMessageEvent = async function attachEditMessageEvent(btn) { + const messageEl = btn.parentElement.parentElement.parentElement; + + const authorAlias = (await apx.crypto.decryptMessage((await apx.indexedDB.get("privatri", "messages", `${threadPageEl.getAttribute("data-uuid")}_${messageEl.getAttribute("data-timestamp")}`)).sender_alias, (await apx.indexedDB.get("privatri", "threads", threadPageEl.getAttribute("data-uuid"))).privateKey)).data; + + if (JSON.parse(localStorage.getItem("apx")).data.headers.xalias === authorAlias) { + const messageValue = messageEl.querySelector("div.message").innerText; + + const attachmentsArray = (() => { + const attachmentsArray = []; + + messageEl.querySelector("div.attachmentsContainer").querySelectorAll("img").forEach(img => { + attachmentsArray.push( + { + fileType: img.getAttribute("src").match(/^data:(.*);base64,/)[1], + filename: img.getAttribute("alt"), + content: img.getAttribute("src").split(",")[1] + } + ); + }); + + return attachmentsArray; + })(); + + messageEl.innerHTML = await editMessage(parseInt(messageEl.getAttribute("data-timestamp"), 10), messageEl.querySelector("div.message").innerText); + + messageEl.querySelector("button.cancelEditBtn").addEventListener("click", async () => { + const messageObj = await apx.indexedDB.get("privatri", "messages", `${threadPageEl.getAttribute("data-uuid")}_${messageEl.getAttribute("data-timestamp")}`); + + messageEl.innerHTML = await sendNewMessage(messageObj.timestamp, messageValue, attachmentsArray); + }); + + messageEl.querySelector("button.saveEditBtn").addEventListener("click", async () => { + const newMessageValue = messageEl.querySelector("textarea").value.trim(); + + if (newMessageValue !== "") { + const privateKey = (await apx.indexedDB.get("privatri", "threads", threadPageEl.getAttribute("data-uuid"))).privateKey; + + const messageObj = await apx.indexedDB.get("privatri", "messages", `${threadPageEl.getAttribute("data-uuid")}_${messageEl.getAttribute("data-timestamp")}`); + + messageObj.message = await apx.crypto.encryptMessage(newMessageValue, privateKey), + + await apx.indexedDB.set("privatri", "messages", messageObj); + + messageEl.innerHTML = await sendNewMessage(messageObj.timestamp, newMessageValue, attachmentsArray); + }; + }); + } else { + bodyEl.insertAdjacentHTML("beforeend", await displayToastAlert("You don't have the permissions to edit this message.")); + + setTimeout(() => { + bodyEl.lastElementChild.remove(); + }, 3000); + }; + }; + + window.attachEventDisplayAttachment = function(img) { + let overlay = document.getElementById("imageOverlay"); + + if (overlay === null) { + overlay = document.createElement('div'); + + overlay.id = "imageOverlay"; + overlay.style.position = "fixed"; + overlay.style.top = 0; + overlay.style.left = 0; + overlay.style.width = "100vw"; + overlay.style.height = "100vh"; + overlay.style.background = "rgba(0, 0, 0, 0.75)"; + overlay.style.display = "flex"; + overlay.style.alignItems = "center"; + overlay.style.justifyContent = "center"; + overlay.style.zIndex = 999; + + overlay.onclick = () => { + overlay.remove(); + }; + + document.body.appendChild(overlay); + } else { + overlay.innerHTML = ""; + overlay.style.display = "flex"; + }; + + const fullScreenImage = document.createElement("img"); + + fullScreenImage.src = img.src; + fullScreenImage.alt = img.alt; + fullScreenImage.style.maxWidth = "90vw"; + fullScreenImage.style.maxHeight = "90vh"; + overlay.appendChild(fullScreenImage); + }; + + document.querySelector("#attachmentsBtn").addEventListener("click", () => { + attachmentsInputEl.click(); + }); + + attachmentsInputEl.addEventListener("change", async () => { + const filesArray = Array.from(attachmentsInputEl.files); + const maxFileSize = 5 * 1024 * 1024; + const maxSize = 512; + + for (const file of filesArray) { + if (file.size <= maxFileSize) { + const attachmentObj = await new Promise((resolve, reject) => { + const attachmentObj = { + fileType: file.type, + filename: file.name, + content: "" + }; + + const img = new Image(); + + img.onload = () => { + if (img.width > maxSize || img.height > maxSize) { + let width = img.width; + let height = img.height; + + if (width > maxSize) { + height *= maxSize / width; + width = maxSize; + }; + + if (height > maxSize) { + width *= maxSize / height; + height = maxSize; + }; + + const canvas = document.createElement("canvas"); + canvas.width = width; + canvas.height = height; + + const ctx = canvas.getContext("2d"); + + ctx.drawImage(img, 0, 0, width, height); + + canvas.toBlob(blob => { + const reader = new FileReader(); + + reader.onload = (event) => { + attachmentObj.content = event.target.result.split(",")[1]; + + resolve(attachmentObj); + }; + + reader.onerror = reject; + reader.readAsDataURL(blob); + }, "image/jpeg", 0.8); + } else { + const reader = new FileReader(); + + reader.onload = (event) => { + attachmentObj.content = event.target.result.split(",")[1]; + + resolve(attachmentObj); + }; + + reader.onerror = reject; + reader.readAsDataURL(file); + }; + }; + + img.onerror = reject; + img.src = URL.createObjectURL(file); + + return attachmentObj; + }); + + if (sessionStorage.getItem(`attachmentsArray_${threadPageEl.getAttribute("data-uuid")}`) === null) { + sessionStorage.setItem(`attachmentsArray_${threadPageEl.getAttribute("data-uuid")}`, JSON.stringify([attachmentObj])); + } else { + const attachmentsArray = JSON.parse(sessionStorage.getItem(`attachmentsArray_${threadPageEl.getAttribute("data-uuid")}`)); + attachmentsArray.push(attachmentObj); + + sessionStorage.setItem(`attachmentsArray_${threadPageEl.getAttribute("data-uuid")}`, JSON.stringify(attachmentsArray)); + }; + }; + }; + }); +}); \ No newline at end of file diff --git a/wco/privatri/thread.mustache b/wco/privatri/thread.mustache new file mode 100644 index 0000000..2103725 --- /dev/null +++ b/wco/privatri/thread.mustache @@ -0,0 +1,7 @@ + \ No newline at end of file diff --git a/wco/privatri/threadAliasList.mustache b/wco/privatri/threadAliasList.mustache new file mode 100644 index 0000000..a42e90a --- /dev/null +++ b/wco/privatri/threadAliasList.mustache @@ -0,0 +1,6 @@ +
+

Alias list

+
+ +
+
\ No newline at end of file diff --git a/wco/privatri/threadSettings.mustache b/wco/privatri/threadSettings.mustache new file mode 100644 index 0000000..3ea3a91 --- /dev/null +++ b/wco/privatri/threadSettings.mustache @@ -0,0 +1,33 @@ +
+

Thread settings

+
+ + + + +
+ +
+ +

Urgency deletion

+
+ +
\ No newline at end of file diff --git a/wco/privatri/toastAlert.mustache b/wco/privatri/toastAlert.mustache new file mode 100644 index 0000000..00a4393 --- /dev/null +++ b/wco/privatri/toastAlert.mustache @@ -0,0 +1,5 @@ +
+
+ {{message}} +
+
\ No newline at end of file diff --git a/wwws/admin/src/static/lib/dayjs/plugin/arraySupport.d.ts b/wwws/admin/src/static/lib/dayjs/plugin/arraySupport.d.ts old mode 100755 new mode 100644 diff --git a/wwws/admin/src/static/lib/dayjs/plugin/objectSupport.d.ts b/wwws/admin/src/static/lib/dayjs/plugin/objectSupport.d.ts old mode 100755 new mode 100644 diff --git a/wwws/admin/src/tpldata/privatri_main_privatri.json b/wwws/admin/src/tpldata/privatri_main_privatri.json index 1c240d9..90d4fee 100644 --- a/wwws/admin/src/tpldata/privatri_main_privatri.json +++ b/wwws/admin/src/tpldata/privatri_main_privatri.json @@ -1,19 +1,16 @@ { - "menuapp": [ + "appOptions": [ { "onclick": "onclick='apx.privatri.notificationsMenu();'", - "title": "Notification", - "comment": "./notificationsMenu.html" + "title": "Notifications" }, { - "onclick": "onclick='apx.privatri.addthread();'", - "title": "Nouveau fil", - "comment": "./newMenu3.html" + "title": "New thread", + "template": "createThread" }, { - "onclick": "href='apxid_fr.html'", - "title": "Compte", - "comment": "" + "title": "Account", + "template": "" } ], "searchplaceholder": "Recherche parmis les fils", @@ -26,5 +23,19 @@ "title": "Tri par NOM DE FIL", "onclick": "onclick='apx.privatri.sortby('bytitlethread');'" } + ], + "threadOptions": [ + { + "title": "Invite an alias", + "template": "inviteAlias" + }, + { + "title": "See all alias", + "template": "threadAliasList" + }, + { + "title": "Settings", + "template": "threadSettings" + } ] } \ No newline at end of file diff --git a/wwws/apx/src/static/lib/dayjs/plugin/arraySupport.d.ts b/wwws/apx/src/static/lib/dayjs/plugin/arraySupport.d.ts old mode 100755 new mode 100644 diff --git a/wwws/apx/src/static/lib/dayjs/plugin/objectSupport.d.ts b/wwws/apx/src/static/lib/dayjs/plugin/objectSupport.d.ts old mode 100755 new mode 100644 diff --git a/wwws/itm/admin.json b/wwws/itm/admin.json index 05cc34e..f5ae830 100644 --- a/wwws/itm/admin.json +++ b/wwws/itm/admin.json @@ -80,10 +80,21 @@ "anonymous" ], "tpl": { - "privatrimain": "apxtri/objects/wco/privatri/main.mustache" + "privatrimain": "apxtri/objects/wco/privatri/main.mustache", + "privatriAlias": "apxtri/objects/wco/privatri/alias.mustache", + "privatriCreateThread": "apxtri/objects/wco/privatri/createThread.mustache", + "privatriEditMessage": "apxtri/objects/wco/privatri/editMessage.mustache", + "privatriInviteAlias": "apxtri/objects/wco/privatri/inviteAlias.mustache", + "privatriMessage": "apxtri/objects/wco/privatri/message.mustache", + "privatriThread": "apxtri/objects/wco/privatri/thread.mustache", + "privatriThreadAlias": "apxtri/objects/wco/privatri/threadAlias.mustache", + "privatriThreadSettings": "apxtri/objects/wco/privatri/threadSettings.mustache", + "privatriToastAlert": "apxtri/objects/wco/privatri/toastAlert.mustache", + "privatriThreadAliasList": "apxtri/objects/wco/privatri/threadAliasList.mustache" }, "tpldata": { - "privatri_main_privatri": "apxtri/objects/wwws/admin/src/tpldata/privatri_main_privatri" + "privatri_main_privatri": "apxtri/objects/wwws/admin/src/tpldata/privatri_main_privatri", + "privatri_privatrimain_privatri": "apxtri/objects/wwws/admin/src/tpldata/privatri_privatrimain_privatri" }, "schema": [], "ref": {