modifprivatri wco and page
This commit is contained in:
17
wco/itm/privatri.json
Normal file
17
wco/itm/privatri.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"wconame": "privatri",
|
||||
"owner": "philc",
|
||||
"price": 0,
|
||||
"aliascode": [],
|
||||
"codehash": "123",
|
||||
"thumbnail": "",
|
||||
"title": "Gestion des accès privés",
|
||||
"description": "Composant pour gérer les accès et les données privées.",
|
||||
"lang": ["fr"],
|
||||
"tpl": {
|
||||
"privatrimain": "apxtri/objects/wco/privatri/main.mustache"
|
||||
},
|
||||
"tpldatamodel": { "privatri": "apxtri/objects/wco/privatri/exampleprivatri" },
|
||||
"schema": [],
|
||||
"ref": {}
|
||||
}
|
9
wco/privatri/exampleprivatri.json
Normal file
9
wco/privatri/exampleprivatri.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"title": "Titre du Composant Privatri",
|
||||
"description": "Ceci est la description par défaut du composant, chargée depuis son modèle de données d'exemple.",
|
||||
"items": [
|
||||
"Élément 1 par défaut",
|
||||
"Élément 2 par défaut",
|
||||
"Élément 3 par défaut"
|
||||
]
|
||||
}
|
88
wco/privatri/main.mustache
Normal file
88
wco/privatri/main.mustache
Normal file
@@ -0,0 +1,88 @@
|
||||
<section class="w-fit min-w-110 h-screen max-h-screen bg-base-100 border-r border-solid border-r-base-300 relative flex flex-col">
|
||||
<div class="w-full h-12 max-h-12 flex items-center justify-evenly mt-4 mb-4 flex-shrink-0">
|
||||
<div class="dropdown h-full">
|
||||
<div tabindex="0" role="button" class="h-full flex items-center">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="32px" viewBox="0 -960 960 960" width="32px" class="h-full fill-current text-base-content">
|
||||
<path d="M120-240v-80h720v80H120Zm0-200v-80h720v80H120Zm0-200v-80h720v80H120Z"/>
|
||||
</svg>
|
||||
</div>
|
||||
<ul tabindex="0" class="dropdown-content menu bg-base-300 rounded-box z-1 w-52 p-2 shadow-sm mt-2">
|
||||
{{#menuapp}}
|
||||
<li>
|
||||
<a {{onclick}} class="rounded-lg">{{{title}}}</a>
|
||||
</li>
|
||||
{{/menuapp}}
|
||||
</ul>
|
||||
</div>
|
||||
<label class="input rounded-lg md-4">
|
||||
<svg class="h-4 w-4 text-base-content" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<g stroke-linejoin="round" stroke-linecap="round" stroke-width="2.5" fill="none" stroke="currentColor">
|
||||
<circle cx="11" cy="11" r="8"></circle>
|
||||
<path d="m21 21-4.3-4.3"></path>
|
||||
</g>
|
||||
</svg>
|
||||
<input id="threadSearchBar" type="search" required placeholder="{{{seacheplaceholder}}}" class="flex-1 bg-transparent outline-none placeholder:opacity-75 placeholder:text-base-content text-base-content" />
|
||||
</label>
|
||||
<div class="dropdown dropdown-end">
|
||||
<div tabindex="0" role="button" class="m-1">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="28px" viewBox="0 -960 960 960" width="28px" class="h-full fill-current text-base-content">
|
||||
<path d="M440-160q-17 0-28.5-11.5T400-200v-240L168-736q-15-20-4.5-42t36.5-22h560q26 0 36.5 22t-4.5 42L560-440v240q0 17-11.5 28.5T520-160h-80Zm40-308 198-252H282l198 252Zm0 0Z"/>
|
||||
</svg>
|
||||
</div>
|
||||
<ul id="threadFilterOptions" tabindex="0" class="dropdown-content menu bg-base-300 rounded-box z-1 w-52 p-2 shadow-sm mt-2">
|
||||
{{#sortoptions}}
|
||||
<li>
|
||||
<p class="rounded-lg" {{onclick}} >{{onclick}}</p>
|
||||
</li>
|
||||
{{/sortoptions}}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div id="threadsContainer" class="flex-1 overflow-y-auto flex flex-col"></div>
|
||||
<a href="./newMenu3.html" class="w-16 h-16 btn bg-base-content rounded-full p-0 shadow-lg flex items-center justify-center absolute bottom-8 right-8">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="32px" viewBox="0 -960 960 960" width="32px" class="h-full fill-current text-base-100">
|
||||
<path d="M120-160v-600q0-33 23.5-56.5T200-840h480q33 0 56.5 23.5T760-760v203q-10-2-20-2.5t-20-.5q-10 0-20 .5t-20 2.5v-203H200v400h283q-2 10-2.5 20t-.5 20q0 10 .5 20t2.5 20H240L120-160Zm160-440h320v-80H280v80Zm0 160h200v-80H280v80Zm400 280v-120H560v-80h120v-120h80v120h120v80H760v120h-80ZM200-360v-400 400Z"/>
|
||||
</svg>
|
||||
</a>
|
||||
</section>
|
||||
<section id="threadPage" class="w-full flex-1 bg-base-100 flex flex-col h-screen">
|
||||
<div class="w-full h-12 max-h-12 mt-4 mb-4 flex items-center justify-center pl-8 pr-8">
|
||||
<div class="flex-1 flex items-center justify-center">
|
||||
<span id="threadName" class="text-xl">{threadName}</span>
|
||||
</div>
|
||||
<div class="dropdown dropdown-end">
|
||||
<div tabindex="0" role="button" class="m-1">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="32px" viewBox="0 -960 960 960" width="32px" class="h-full fill-current text-base-content">
|
||||
<path d="M240-400q-33 0-56.5-23.5T160-480q0-33 23.5-56.5T240-560q33 0 56.5 23.5T320-480q0 33-23.5 56.5T240-400Zm240 0q-33 0-56.5-23.5T400-480q0-33 23.5-56.5T480-560q33 0 56.5 23.5T560-480q0 33-23.5 56.5T480-400Zm240 0q-33 0-56.5-23.5T640-480q0-33 23.5-56.5T720-560q33 0 56.5 23.5T800-480q0 33-23.5 56.5T720-400Z"/>
|
||||
</svg>
|
||||
</div>
|
||||
<ul tabindex="0" class="dropdown-content menu bg-base-300 rounded-box z-1 w-52 p-2 shadow-sm">
|
||||
<li>
|
||||
<a href="./inviteAllias.html" class="rounded-lg">Invite an alias</a>
|
||||
</li>
|
||||
<li>
|
||||
<a id="aliasListHyperlink" class="rounded-lg">See all alias</a>
|
||||
</li>
|
||||
<li>
|
||||
<a id="settingsHyperlink" class="rounded-lg">Settings</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div id="messagesContainer" class="w-full flex-1 min-h-0 bg-base-200 overflow-y-auto flex flex-col p-4 gap-y-6"></div>
|
||||
<div class="flex items-center justify-center w-full h-fit pt-4 pb-4 pl-8 pr-8 gap-x-4">
|
||||
<button id="attachmentsBtn" class="hover:bg-base-200 transition rounded-full p-2 flex items-center justify-center">
|
||||
<input id="attachmentsInput" type="file" multiple accept="image/png, image/jpeg" class="hidden"/>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="28px" viewBox="0 -960 960 960" width="28px" class="h-full fill-current text-base-content opacity-50">
|
||||
<path d="M720-330q0 104-73 177T470-80q-104 0-177-73t-73-177v-370q0-75 52.5-127.5T400-880q75 0 127.5 52.5T580-700v350q0 46-32 78t-78 32q-46 0-78-32t-32-78v-370h80v370q0 13 8.5 21.5T470-320q13 0 21.5-8.5T500-350v-350q-1-42-29.5-71T400-800q-42 0-71 29t-29 71v370q-1 71 49 120.5T470-160q70 0 119-49.5T640-330v-390h80v390Z"/>
|
||||
</svg>
|
||||
</button>
|
||||
<div class="flex items-center rounded-full w-full h-10 bg-base-300">
|
||||
<input id="messageInput" type="text" placeholder="Write a message..." class="flex-1 bg-transparent border-transparent outline-none px-4" />
|
||||
<button id="sendMessageBtn" type="button" class="flex justify-center items-center ml-2 p-2 rounded-full hover:bg-base-200 transition">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="28px" viewBox="0 -960 960 960" width="28px" class="h-full fill-current text-base-content opacity-50">
|
||||
<path d="M120-160v-640l760 320-760 320Zm80-120 474-200-474-200v140l240 60-240 60v140Zm0 0v-400 400Z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
@@ -1,506 +1,25 @@
|
||||
import apx from "./apx.js";
|
||||
import { getOldestPrivatriids, syncronizeBackend } from "./utils.js";
|
||||
|
||||
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) => {
|
||||
let messageTemplate = "";
|
||||
|
||||
await fetch("./message.mustache")
|
||||
.then(res => res.text())
|
||||
.then(template => {
|
||||
messageTemplate = template;
|
||||
});
|
||||
|
||||
messageTemplate = Mustache.render(messageTemplate, { 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", `<img onclick="attachEventDisplayAttachment(this)" class="w-20 h-20 object-cover rounded" src="data:${attachment.fileType};base64,${attachment.content}" alt="${attachment.filename}"/>`);
|
||||
});
|
||||
|
||||
return messageNode.outerHTML;
|
||||
};
|
||||
|
||||
const editMessage = async (timestamp, message) => {
|
||||
let editMessageTemplate = "";
|
||||
|
||||
await fetch("./editMessage.mustache")
|
||||
.then(res => res.text())
|
||||
.then(template => {
|
||||
editMessageTemplate = template;
|
||||
});
|
||||
|
||||
return Mustache.render(editMessageTemplate, { message, date: formatDate(timestamp) });
|
||||
};
|
||||
|
||||
const displayToastAlert = async (message) => {
|
||||
let toastAlertTemplate = "";
|
||||
|
||||
await fetch("./toastAlert.mustache")
|
||||
.then(res => res.text())
|
||||
.then(template => {
|
||||
toastAlertTemplate = template;
|
||||
});
|
||||
|
||||
return Mustache.render(toastAlertTemplate, { 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)
|
||||
}
|
||||
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
|
||||
)}`
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
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);
|
||||
};
|
||||
});
|
||||
|
||||
document.addEventListener("DOMContentLoaded", async () => {
|
||||
const lastConnection = JSON.parse(localStorage.getItem("lastConnection")) || Date.now();
|
||||
|
||||
await syncronizeBackend(lastConnection);
|
||||
|
||||
const privatriidArray = await getOldestPrivatriids("privatri", "messages");
|
||||
|
||||
const thread = async (name, uuid) => {
|
||||
let threadTemplate = "";
|
||||
|
||||
await fetch("./thread.mustache")
|
||||
.then(res => res.text())
|
||||
.then(template => {
|
||||
threadTemplate = template;
|
||||
});
|
||||
|
||||
return Mustache.render(threadTemplate, { 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")}`);
|
||||
};
|
||||
|
||||
await fetch(`./${templateName}.mustache`)
|
||||
.then(res => res.text())
|
||||
.then(template => {
|
||||
template = Mustache.render(template, {});
|
||||
|
||||
bodyEl.innerHTML = template;
|
||||
});
|
||||
|
||||
const script = document.createElement("script");
|
||||
|
||||
script.type = "module";
|
||||
script.src = `./${templateName}.js`;
|
||||
|
||||
bodyEl.appendChild(script);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
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));
|
||||
};
|
||||
};
|
||||
};
|
||||
});
|
||||
//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.getthread=async(alias,timestamp)=>{
|
||||
//store in indexdb the thread
|
||||
return true
|
||||
}
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
506
wco/privatri_mathieu/privatri.js
Normal file
506
wco/privatri_mathieu/privatri.js
Normal file
@@ -0,0 +1,506 @@
|
||||
import apx from "./apx.js";
|
||||
import { getOldestPrivatriids, syncronizeBackend } from "./utils.js";
|
||||
|
||||
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) => {
|
||||
let messageTemplate = "";
|
||||
|
||||
await fetch("./message.mustache")
|
||||
.then(res => res.text())
|
||||
.then(template => {
|
||||
messageTemplate = template;
|
||||
});
|
||||
|
||||
messageTemplate = Mustache.render(messageTemplate, { 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", `<img onclick="attachEventDisplayAttachment(this)" class="w-20 h-20 object-cover rounded" src="data:${attachment.fileType};base64,${attachment.content}" alt="${attachment.filename}"/>`);
|
||||
});
|
||||
|
||||
return messageNode.outerHTML;
|
||||
};
|
||||
|
||||
const editMessage = async (timestamp, message) => {
|
||||
let editMessageTemplate = "";
|
||||
|
||||
await fetch("./editMessage.mustache")
|
||||
.then(res => res.text())
|
||||
.then(template => {
|
||||
editMessageTemplate = template;
|
||||
});
|
||||
|
||||
return Mustache.render(editMessageTemplate, { message, date: formatDate(timestamp) });
|
||||
};
|
||||
|
||||
const displayToastAlert = async (message) => {
|
||||
let toastAlertTemplate = "";
|
||||
|
||||
await fetch("./toastAlert.mustache")
|
||||
.then(res => res.text())
|
||||
.then(template => {
|
||||
toastAlertTemplate = template;
|
||||
});
|
||||
|
||||
return Mustache.render(toastAlertTemplate, { 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);
|
||||
};
|
||||
});
|
||||
|
||||
document.addEventListener("DOMContentLoaded", async () => {
|
||||
const lastConnection = JSON.parse(localStorage.getItem("lastConnection")) || Date.now();
|
||||
|
||||
await syncronizeBackend(lastConnection);
|
||||
|
||||
const privatriidArray = await getOldestPrivatriids("privatri", "messages");
|
||||
|
||||
const thread = async (name, uuid) => {
|
||||
let threadTemplate = "";
|
||||
|
||||
await fetch("./thread.mustache")
|
||||
.then(res => res.text())
|
||||
.then(template => {
|
||||
threadTemplate = template;
|
||||
});
|
||||
|
||||
return Mustache.render(threadTemplate, { 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")}`);
|
||||
};
|
||||
|
||||
await fetch(`./${templateName}.mustache`)
|
||||
.then(res => res.text())
|
||||
.then(template => {
|
||||
template = Mustache.render(template, {});
|
||||
|
||||
bodyEl.innerHTML = template;
|
||||
});
|
||||
|
||||
const script = document.createElement("script");
|
||||
|
||||
script.type = "module";
|
||||
script.src = `./${templateName}.js`;
|
||||
|
||||
bodyEl.appendChild(script);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
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));
|
||||
};
|
||||
};
|
||||
};
|
||||
});
|
57
wwws/admin/src/privatri_fr.html
Normal file
57
wwws/admin/src/privatri_fr.html
Normal file
@@ -0,0 +1,57 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr" data-theme="apxtridark" class="h-full bg-base-200 text-neutral-content ">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Accès Privé</title>
|
||||
<meta content="width=device-width, initial-scale=1.0" name="viewport" />
|
||||
<meta
|
||||
content="gestion, accès, privé, apxtri"
|
||||
name="keywords"
|
||||
/>
|
||||
<meta
|
||||
content="Page de gestion des accès privés."
|
||||
name="description"
|
||||
/>
|
||||
<link data-wco="favicon" href="static/img/icons/iconbgdark.png" rel="icon" />
|
||||
<link href="static/css/output.css" rel="stylesheet" />
|
||||
<script>
|
||||
/**
|
||||
* Read apx.js to know more
|
||||
*/
|
||||
const apxtri = {
|
||||
headers: {
|
||||
xtrkversion: 1,
|
||||
xtribe: "apxtri",
|
||||
xapp: "admin",
|
||||
xlang: "fr",
|
||||
xalias: "anonymous",
|
||||
xhash: "anonymous",
|
||||
xprofils:["anonymous"],
|
||||
xdays: 0,
|
||||
xuuid:0
|
||||
},
|
||||
pagename: "privatri",
|
||||
pageauth: "apxid",
|
||||
wcoobserver:true,
|
||||
allowedprofils:["pagans"],
|
||||
version:0
|
||||
};
|
||||
</script>
|
||||
<script src="/apxtrilib/axios/dist/axios.min.js"></script>
|
||||
<script src="/apxtrilib/dayjs/dayjs.min.js"></script>
|
||||
<script src="/apxtrilib/openpgp/dist/openpgp.min.js"></script>
|
||||
<script src="/apxtrilib/mustache/mustache.min.js"></script>
|
||||
<script src="/apxtrilib/qr-code-styling/lib/qr-code-styling.js"></script>
|
||||
<script src="/apxtrilib/checkjson.js"></script>
|
||||
|
||||
<script src="/api/apxtri/wwws/getwco/apx.js?wcotribe=apxtri&tribe=apxtri&xapp=admin&pagename=privatri&code=enjoy"></script>
|
||||
<script src="/api/apxtri/wwws/getwco/privatri.js?wcotribe=apxtri&tribe=apxtri&xapp=admin&pagename=privatri&code=enjoy&tagid=main"></script>
|
||||
</head>
|
||||
<body class="bg-base-100 flex items-center justify-center h-screen w-screen ">
|
||||
<div
|
||||
id="main"
|
||||
wco-name="privatri"
|
||||
>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
30
wwws/admin/src/tpldata/privatri_main_privatri.json
Normal file
30
wwws/admin/src/tpldata/privatri_main_privatri.json
Normal file
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"menuapp": [
|
||||
{
|
||||
"onclick": "onclick='apx.privatri.notificationsMenu();'",
|
||||
"title": "Notification",
|
||||
"comment": "./notificationsMenu.html"
|
||||
},
|
||||
{
|
||||
"onclick": "onclick='apx.privatri.addthread();'",
|
||||
"title": "Nouveau fil",
|
||||
"comment": "./newMenu3.html"
|
||||
},
|
||||
{
|
||||
"onclick": "href='apxid_fr.html'",
|
||||
"title": "Compte",
|
||||
"comment": ""
|
||||
}
|
||||
],
|
||||
"searchplaceholder": "Recherche parmis les fils",
|
||||
"sortoptions": [
|
||||
{
|
||||
"title": "Tri par date",
|
||||
"onclick": "onclick='apx.privatri.sortby('bydate');'"
|
||||
},
|
||||
{
|
||||
"title": "Tri par NOM DE FIL",
|
||||
"onclick": "onclick='apx.privatri.sortby('bytitlethread');'"
|
||||
}
|
||||
]
|
||||
}
|
@@ -73,6 +73,22 @@
|
||||
"Pagans": "apxtri/models//tplstrings/Pagans",
|
||||
"Persons": "apxtri/models/tplstrings/Persons"
|
||||
}
|
||||
},
|
||||
"privatri": {
|
||||
"version": 1,
|
||||
"profils": [
|
||||
"anonymous"
|
||||
],
|
||||
"tpl": {
|
||||
"privatrimain": "apxtri/objects/wco/privatri/main.mustache"
|
||||
},
|
||||
"tpldata": {
|
||||
"privatri_main_privatri": "apxtri/objects/wwws/admin/src/tpldata/privatri_main_privatri"
|
||||
},
|
||||
"schema": [],
|
||||
"ref": {},
|
||||
"wco": {},
|
||||
"appdata": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user