@bobfrankston/rmfmail 1.2.229 → 1.2.230
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/client/app.bundle.js +349 -66
- package/client/app.bundle.js.map +4 -4
- package/client/app.js +16 -2
- package/client/app.js.map +1 -1
- package/client/app.ts +14 -1
- package/client/components/calendar-sidebar.js +1 -1
- package/client/components/calendar-sidebar.js.map +1 -1
- package/client/components/calendar-sidebar.ts +1 -1
- package/client/components/context-menu.js +12 -1
- package/client/components/context-menu.js.map +1 -1
- package/client/components/context-menu.ts +36 -1
- package/client/components/edit-menu.js +339 -0
- package/client/components/edit-menu.js.map +1 -0
- package/client/components/edit-menu.ts +343 -0
- package/client/components/folder-tree.js +2 -2
- package/client/components/folder-tree.js.map +1 -1
- package/client/components/folder-tree.ts +2 -2
- package/client/components/message-list.js +3 -1
- package/client/components/message-list.js.map +1 -1
- package/client/components/message-list.ts +3 -1
- package/client/compose/compose.bundle.js +258 -83
- package/client/compose/compose.bundle.js.map +4 -4
- package/client/compose/compose.js +5 -28
- package/client/compose/compose.js.map +1 -1
- package/client/compose/compose.ts +4 -13
- package/client/compose/edit-commands.js +15 -89
- package/client/compose/edit-commands.js.map +1 -1
- package/client/compose/edit-commands.ts +17 -77
- package/package.json +1 -1
- /package/packages/mailx-imap/{node_modules.npmglobalize-stash-146480 → node_modules.npmglobalize-stash-38284}/.package-lock.json +0 -0
package/client/app.bundle.js
CHANGED
|
@@ -717,6 +717,276 @@ var init_api_client = __esm({
|
|
|
717
717
|
}
|
|
718
718
|
});
|
|
719
719
|
|
|
720
|
+
// client/components/edit-menu.js
|
|
721
|
+
function isTinyEngine(ne) {
|
|
722
|
+
return !!ne && typeof ne.execCommand === "function" && !!ne.selection;
|
|
723
|
+
}
|
|
724
|
+
function describeEditTarget(target) {
|
|
725
|
+
const none = { el: null, editable: false, selection: "", canRead: true, isField: false };
|
|
726
|
+
const node = target;
|
|
727
|
+
if (!node)
|
|
728
|
+
return { ...none, selection: pageSelection() };
|
|
729
|
+
const start = node.nodeType === Node.ELEMENT_NODE ? node : node.parentElement;
|
|
730
|
+
if (!start)
|
|
731
|
+
return { ...none, selection: pageSelection() };
|
|
732
|
+
const field = start.closest("input, textarea");
|
|
733
|
+
if (field) {
|
|
734
|
+
const type = (field instanceof HTMLInputElement ? field.type : "text").toLowerCase();
|
|
735
|
+
if (!TEXTUAL_INPUT_TYPES.has(type))
|
|
736
|
+
return { ...none, selection: pageSelection() };
|
|
737
|
+
const s = field.selectionStart ?? 0, e = field.selectionEnd ?? 0;
|
|
738
|
+
return {
|
|
739
|
+
el: field,
|
|
740
|
+
editable: !field.readOnly && !field.disabled,
|
|
741
|
+
selection: e > s ? field.value.slice(s, e) : "",
|
|
742
|
+
canRead: type !== "password",
|
|
743
|
+
isField: true
|
|
744
|
+
};
|
|
745
|
+
}
|
|
746
|
+
const ce = start.closest("[contenteditable]");
|
|
747
|
+
const editable = !!ce && ce.isContentEditable;
|
|
748
|
+
return { el: ce || start, editable, selection: pageSelection(), canRead: true, isField: false };
|
|
749
|
+
}
|
|
750
|
+
function pageSelection() {
|
|
751
|
+
try {
|
|
752
|
+
return window.getSelection()?.toString() || "";
|
|
753
|
+
} catch {
|
|
754
|
+
return "";
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
function escapeHtml(s) {
|
|
758
|
+
return s.replace(/[&<>]/g, (c) => ({ "&": "&", "<": "<", ">": ">" })[c]);
|
|
759
|
+
}
|
|
760
|
+
async function readClipboard() {
|
|
761
|
+
let html = "", text = "";
|
|
762
|
+
if (navigator.clipboard?.read) {
|
|
763
|
+
for (const item of await navigator.clipboard.read()) {
|
|
764
|
+
if (item.types.includes("text/html"))
|
|
765
|
+
html = await (await item.getType("text/html")).text();
|
|
766
|
+
if (item.types.includes("text/plain"))
|
|
767
|
+
text = await (await item.getType("text/plain")).text();
|
|
768
|
+
}
|
|
769
|
+
if (!html && !text)
|
|
770
|
+
text = await navigator.clipboard.readText();
|
|
771
|
+
} else {
|
|
772
|
+
text = await navigator.clipboard.readText();
|
|
773
|
+
}
|
|
774
|
+
return { html, text };
|
|
775
|
+
}
|
|
776
|
+
async function writeClipboard(text, html) {
|
|
777
|
+
if (html && typeof ClipboardItem === "function" && navigator.clipboard?.write) {
|
|
778
|
+
await navigator.clipboard.write([new ClipboardItem({
|
|
779
|
+
"text/html": new Blob([html], { type: "text/html" }),
|
|
780
|
+
"text/plain": new Blob([text], { type: "text/plain" })
|
|
781
|
+
})]);
|
|
782
|
+
return;
|
|
783
|
+
}
|
|
784
|
+
await navigator.clipboard.writeText(text);
|
|
785
|
+
}
|
|
786
|
+
function expandToWord(ne) {
|
|
787
|
+
if (isTinyEngine(ne)) {
|
|
788
|
+
try {
|
|
789
|
+
if (ne.selection.isCollapsed() && typeof ne.selection.expand === "function") {
|
|
790
|
+
ne.selection.expand({ type: "word" });
|
|
791
|
+
}
|
|
792
|
+
} catch {
|
|
793
|
+
}
|
|
794
|
+
return;
|
|
795
|
+
}
|
|
796
|
+
if (ne && typeof ne.getSelection === "function" && typeof ne.setSelection === "function") {
|
|
797
|
+
const sel = ne.getSelection();
|
|
798
|
+
if (!sel || sel.length > 0)
|
|
799
|
+
return;
|
|
800
|
+
const text = ne.getText();
|
|
801
|
+
let a = sel.index, b = sel.index;
|
|
802
|
+
while (a > 0 && /\S/.test(text[a - 1]))
|
|
803
|
+
a--;
|
|
804
|
+
while (b < text.length && /\S/.test(text[b]))
|
|
805
|
+
b++;
|
|
806
|
+
if (b > a)
|
|
807
|
+
ne.setSelection(a, b - a);
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
function replaceFieldSelection(field, text) {
|
|
811
|
+
const s = field.selectionStart ?? field.value.length;
|
|
812
|
+
const e = field.selectionEnd ?? field.value.length;
|
|
813
|
+
field.value = field.value.slice(0, s) + text + field.value.slice(e);
|
|
814
|
+
field.selectionStart = field.selectionEnd = s + text.length;
|
|
815
|
+
field.dispatchEvent(new Event("input", { bubbles: true }));
|
|
816
|
+
}
|
|
817
|
+
async function runClipboard(ctx, id) {
|
|
818
|
+
if (ctx.engine)
|
|
819
|
+
return runEngineClipboard(ctx.engine, id);
|
|
820
|
+
const info = describeEditTarget(ctx.target);
|
|
821
|
+
if (id === "paste") {
|
|
822
|
+
if (!info.editable || !info.el)
|
|
823
|
+
throw new Error("nothing here accepts a paste");
|
|
824
|
+
const { html, text } = await readClipboard();
|
|
825
|
+
if (info.isField) {
|
|
826
|
+
if (!text && !html)
|
|
827
|
+
return;
|
|
828
|
+
replaceFieldSelection(info.el, text || stripHtml(html));
|
|
829
|
+
return;
|
|
830
|
+
}
|
|
831
|
+
const content = html || (text ? escapeHtml(text).replace(/\r?\n/g, "<br>") : "");
|
|
832
|
+
if (!content)
|
|
833
|
+
return;
|
|
834
|
+
insertHtmlAtSelection(info.el, content);
|
|
835
|
+
return;
|
|
836
|
+
}
|
|
837
|
+
if (!info.canRead)
|
|
838
|
+
throw new Error("this field's contents can't be copied");
|
|
839
|
+
if (!info.selection)
|
|
840
|
+
return;
|
|
841
|
+
if (info.isField) {
|
|
842
|
+
await writeClipboard(info.selection);
|
|
843
|
+
} else {
|
|
844
|
+
await writeClipboard(info.selection, selectionHtml());
|
|
845
|
+
}
|
|
846
|
+
if (id !== "cut")
|
|
847
|
+
return;
|
|
848
|
+
if (!info.editable)
|
|
849
|
+
throw new Error("this text is read-only \u2014 copied instead of cut");
|
|
850
|
+
if (info.isField) {
|
|
851
|
+
replaceFieldSelection(info.el, "");
|
|
852
|
+
return;
|
|
853
|
+
}
|
|
854
|
+
try {
|
|
855
|
+
window.getSelection()?.deleteFromDocument();
|
|
856
|
+
} catch {
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
async function runEngineClipboard(ne, id) {
|
|
860
|
+
const isTiny = isTinyEngine(ne);
|
|
861
|
+
if (id === "paste") {
|
|
862
|
+
const { html: html2, text: text2 } = await readClipboard();
|
|
863
|
+
const content = html2 || (text2 ? escapeHtml(text2).replace(/\r?\n/g, "<br>") : "");
|
|
864
|
+
if (!content)
|
|
865
|
+
return;
|
|
866
|
+
if (isTiny) {
|
|
867
|
+
ne.execCommand("mceInsertContent", false, content);
|
|
868
|
+
return;
|
|
869
|
+
}
|
|
870
|
+
if (ne?.clipboard?.dangerouslyPasteHTML) {
|
|
871
|
+
const sel = ne.getSelection(true);
|
|
872
|
+
ne.clipboard.dangerouslyPasteHTML(sel?.index ?? 0, content);
|
|
873
|
+
return;
|
|
874
|
+
}
|
|
875
|
+
throw new Error("editor doesn't support paste");
|
|
876
|
+
}
|
|
877
|
+
expandToWord(ne);
|
|
878
|
+
let html = "", text = "";
|
|
879
|
+
if (isTiny) {
|
|
880
|
+
html = ne.selection.getContent({ format: "html" });
|
|
881
|
+
text = ne.selection.getContent({ format: "text" });
|
|
882
|
+
} else {
|
|
883
|
+
text = pageSelection();
|
|
884
|
+
html = selectionHtml();
|
|
885
|
+
}
|
|
886
|
+
if (!text && !html)
|
|
887
|
+
return;
|
|
888
|
+
await writeClipboard(text, html || void 0);
|
|
889
|
+
if (id !== "cut")
|
|
890
|
+
return;
|
|
891
|
+
if (isTiny) {
|
|
892
|
+
ne.execCommand("Delete");
|
|
893
|
+
return;
|
|
894
|
+
}
|
|
895
|
+
if (typeof ne?.deleteText === "function") {
|
|
896
|
+
const sel = ne.getSelection();
|
|
897
|
+
if (sel?.length)
|
|
898
|
+
ne.deleteText(sel.index, sel.length);
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
function selectionHtml() {
|
|
902
|
+
try {
|
|
903
|
+
const sel = window.getSelection();
|
|
904
|
+
if (!sel || sel.rangeCount === 0 || sel.isCollapsed)
|
|
905
|
+
return "";
|
|
906
|
+
const div = document.createElement("div");
|
|
907
|
+
div.appendChild(sel.getRangeAt(0).cloneContents());
|
|
908
|
+
return div.innerHTML;
|
|
909
|
+
} catch {
|
|
910
|
+
return "";
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
function stripHtml(html) {
|
|
914
|
+
const div = document.createElement("div");
|
|
915
|
+
div.innerHTML = html;
|
|
916
|
+
return div.textContent || "";
|
|
917
|
+
}
|
|
918
|
+
function insertHtmlAtSelection(host, html) {
|
|
919
|
+
const sel = window.getSelection();
|
|
920
|
+
const frag = document.createRange().createContextualFragment(html);
|
|
921
|
+
if (!sel || sel.rangeCount === 0) {
|
|
922
|
+
host?.appendChild(frag);
|
|
923
|
+
return;
|
|
924
|
+
}
|
|
925
|
+
const range = sel.getRangeAt(0);
|
|
926
|
+
range.deleteContents();
|
|
927
|
+
const last = frag.lastChild;
|
|
928
|
+
range.insertNode(frag);
|
|
929
|
+
if (last) {
|
|
930
|
+
range.setStartAfter(last);
|
|
931
|
+
range.collapse(true);
|
|
932
|
+
sel.removeAllRanges();
|
|
933
|
+
sel.addRange(range);
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
function selectAll(info) {
|
|
937
|
+
if (info.isField && info.el) {
|
|
938
|
+
info.el.select();
|
|
939
|
+
return;
|
|
940
|
+
}
|
|
941
|
+
const host = info.el;
|
|
942
|
+
if (!host)
|
|
943
|
+
return;
|
|
944
|
+
const range = document.createRange();
|
|
945
|
+
range.selectNodeContents(host);
|
|
946
|
+
const sel = window.getSelection();
|
|
947
|
+
sel?.removeAllRanges();
|
|
948
|
+
sel?.addRange(range);
|
|
949
|
+
}
|
|
950
|
+
function editMenuItems(ctx, opts = {}) {
|
|
951
|
+
const report = opts.onError || ((m) => console.warn(`[edit-menu] ${m}`));
|
|
952
|
+
const info = ctx.engine ? { el: null, editable: true, selection: pageSelection(), canRead: true, isField: false } : describeEditTarget(ctx.target);
|
|
953
|
+
if (!ctx.engine && !info.editable && !info.selection)
|
|
954
|
+
return [];
|
|
955
|
+
const run = (id) => () => {
|
|
956
|
+
void runClipboard(ctx, id).catch((e) => {
|
|
957
|
+
const key = id === "cut" ? "Ctrl+X" : id === "copy" ? "Ctrl+C" : "Ctrl+V";
|
|
958
|
+
const what = id[0].toUpperCase() + id.slice(1);
|
|
959
|
+
report(`${what} failed: ${e?.message || e}. ${key} still works.`);
|
|
960
|
+
});
|
|
961
|
+
};
|
|
962
|
+
const hasSel = !!ctx.engine || !!info.selection;
|
|
963
|
+
const items = [
|
|
964
|
+
{ label: "Cut", action: run("cut"), disabled: !info.editable || !hasSel || !info.canRead },
|
|
965
|
+
{ label: "Copy", action: run("copy"), disabled: !hasSel || !info.canRead },
|
|
966
|
+
{ label: "Paste", action: run("paste"), disabled: !info.editable }
|
|
967
|
+
];
|
|
968
|
+
if (opts.selectAll !== false && !ctx.engine) {
|
|
969
|
+
items.push({ label: "Select all", action: () => selectAll(info) });
|
|
970
|
+
}
|
|
971
|
+
return items;
|
|
972
|
+
}
|
|
973
|
+
var TEXTUAL_INPUT_TYPES;
|
|
974
|
+
var init_edit_menu = __esm({
|
|
975
|
+
"client/components/edit-menu.js"() {
|
|
976
|
+
"use strict";
|
|
977
|
+
TEXTUAL_INPUT_TYPES = /* @__PURE__ */ new Set([
|
|
978
|
+
"text",
|
|
979
|
+
"search",
|
|
980
|
+
"email",
|
|
981
|
+
"url",
|
|
982
|
+
"tel",
|
|
983
|
+
"number",
|
|
984
|
+
"password",
|
|
985
|
+
""
|
|
986
|
+
]);
|
|
987
|
+
}
|
|
988
|
+
});
|
|
989
|
+
|
|
720
990
|
// client/components/context-menu.js
|
|
721
991
|
var context_menu_exports = {};
|
|
722
992
|
__export(context_menu_exports, {
|
|
@@ -783,8 +1053,15 @@ function openSubmenu(parentRow, items) {
|
|
|
783
1053
|
sub.style.top = `${Math.max(4, top)}px`;
|
|
784
1054
|
activeSubmenu = sub;
|
|
785
1055
|
}
|
|
786
|
-
function showContextMenu(x, y, items) {
|
|
1056
|
+
function showContextMenu(x, y, items, opts = {}) {
|
|
787
1057
|
closeContextMenu();
|
|
1058
|
+
if (opts.editTarget !== void 0 || opts.editEngine) {
|
|
1059
|
+
const edit = editMenuItems({ target: opts.editTarget, engine: opts.editEngine }, { onError: opts.onEditError });
|
|
1060
|
+
if (edit.length > 0) {
|
|
1061
|
+
items = items.length > 0 ? [...edit, { label: "", action: () => {
|
|
1062
|
+
}, separator: true }, ...items] : edit;
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
788
1065
|
const menu = document.createElement("div");
|
|
789
1066
|
menu.className = "ctx-menu";
|
|
790
1067
|
for (const item of items) {
|
|
@@ -871,6 +1148,7 @@ var activeMenu, dismissListener, escapeListener, activeSubmenu;
|
|
|
871
1148
|
var init_context_menu = __esm({
|
|
872
1149
|
"client/components/context-menu.js"() {
|
|
873
1150
|
"use strict";
|
|
1151
|
+
init_edit_menu();
|
|
874
1152
|
activeMenu = null;
|
|
875
1153
|
dismissListener = null;
|
|
876
1154
|
escapeListener = null;
|
|
@@ -1132,9 +1410,9 @@ async function openAddressBook(prefillSearch) {
|
|
|
1132
1410
|
<span class="ab-actions"></span>
|
|
1133
1411
|
</div>` + items.map((c) => `
|
|
1134
1412
|
<div class="ab-row" data-email="${escapeAttr(c.email)}">
|
|
1135
|
-
<span class="ab-name" title="${escapeAttr(cardSummary(c))}">${
|
|
1136
|
-
<span class="ab-email">${
|
|
1137
|
-
<span class="ab-source">${
|
|
1413
|
+
<span class="ab-name" title="${escapeAttr(cardSummary(c))}">${escapeHtml2(c.name || "")}${cardSummary(c) ? ' <span class="ab-hascard" aria-hidden="true">\u2022</span>' : ""}</span>
|
|
1414
|
+
<span class="ab-email">${escapeHtml2(c.email)}</span>
|
|
1415
|
+
<span class="ab-source">${escapeHtml2(c.source)}</span>
|
|
1138
1416
|
<span class="ab-count-cell">${c.useCount || 0}</span>
|
|
1139
1417
|
<span class="ab-last">${fmtDate(c.lastUsed)}</span>
|
|
1140
1418
|
<span class="ab-actions">
|
|
@@ -1171,8 +1449,8 @@ async function openAddressBook(prefillSearch) {
|
|
|
1171
1449
|
<input type="email" class="mailx-modal-input" value="${escapeAttr(c.email)}" disabled
|
|
1172
1450
|
title="The address identifies the contact \u2014 delete and re-add to change it"></label>
|
|
1173
1451
|
${CONTACT_FIELDS.map((f) => `
|
|
1174
|
-
<label class="ab-field"><span>${
|
|
1175
|
-
${f.multiline ? `<textarea class="mailx-modal-input" rows="2" data-field="${f.key}">${
|
|
1452
|
+
<label class="ab-field"><span>${escapeHtml2(f.label)}</span>
|
|
1453
|
+
${f.multiline ? `<textarea class="mailx-modal-input" rows="2" data-field="${f.key}">${escapeHtml2(c[f.key] || "")}</textarea>` : `<input type="${f.type || "text"}" class="mailx-modal-input" data-field="${f.key}"
|
|
1176
1454
|
value="${escapeAttr(c[f.key] || "")}"
|
|
1177
1455
|
${f.placeholder ? `placeholder="${escapeAttr(f.placeholder)}"` : ""}>`}
|
|
1178
1456
|
</label>`).join("")}
|
|
@@ -1259,7 +1537,7 @@ async function openAddressBook(prefillSearch) {
|
|
|
1259
1537
|
const r = await listContacts(searchInput2.value, 1, 200);
|
|
1260
1538
|
render2(r.items, r.total);
|
|
1261
1539
|
} catch (e) {
|
|
1262
|
-
listEl.innerHTML = `<div class="ab-empty">Load failed: ${
|
|
1540
|
+
listEl.innerHTML = `<div class="ab-empty">Load failed: ${escapeHtml2(e?.message || String(e))}</div>`;
|
|
1263
1541
|
}
|
|
1264
1542
|
};
|
|
1265
1543
|
const scheduleReload = () => {
|
|
@@ -1308,11 +1586,11 @@ async function openAddressBook(prefillSearch) {
|
|
|
1308
1586
|
function cardSummary(c) {
|
|
1309
1587
|
return CONTACT_FIELDS.map((f) => ({ label: f.label, value: (c[f.key] || "").trim() })).filter((x) => x.value).map((x) => `${x.label}: ${x.value.replace(/\s+/g, " ")}`).join("\n");
|
|
1310
1588
|
}
|
|
1311
|
-
function
|
|
1589
|
+
function escapeHtml2(s) {
|
|
1312
1590
|
return s.replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]);
|
|
1313
1591
|
}
|
|
1314
1592
|
function escapeAttr(s) {
|
|
1315
|
-
return
|
|
1593
|
+
return escapeHtml2(s);
|
|
1316
1594
|
}
|
|
1317
1595
|
var CONTACT_FIELDS, isOpen;
|
|
1318
1596
|
var init_address_book = __esm({
|
|
@@ -1855,7 +2133,7 @@ async function showMessage(accountId, uid, folderId, specialUse, isRetry = false
|
|
|
1855
2133
|
}
|
|
1856
2134
|
if (!cachedMsg) {
|
|
1857
2135
|
const previewText = (cached.preview || "").trim();
|
|
1858
|
-
bodyEl.innerHTML = previewText ? `<div class="mv-preview-placeholder">${
|
|
2136
|
+
bodyEl.innerHTML = previewText ? `<div class="mv-preview-placeholder">${escapeHtml3(previewText)}</div>` : `<div class="mv-empty">Loading body\u2026</div>`;
|
|
1859
2137
|
}
|
|
1860
2138
|
} else if (!cachedMsg) {
|
|
1861
2139
|
bodyEl.innerHTML = `<div class="mv-empty">Loading body\u2026</div>`;
|
|
@@ -1868,7 +2146,7 @@ async function showMessage(accountId, uid, folderId, specialUse, isRetry = false
|
|
|
1868
2146
|
<div class="mv-system-tag">mailx</div>
|
|
1869
2147
|
<div class="mv-system-title">Render failed for this message</div>
|
|
1870
2148
|
<div class="mv-system-body">The display engine stopped while drawing this message last time, so it hasn't been drawn again automatically.
|
|
1871
|
-
Usually this means an unusually large body.${emlHint ? `<br><code style="user-select:all;font-size:0.9em">${
|
|
2149
|
+
Usually this means an unusually large body.${emlHint ? `<br><code style="user-select:all;font-size:0.9em">${escapeHtml3(emlHint)}</code>` : ""}
|
|
1872
2150
|
<br><br><button type="button" id="mv-render-anyway" class="mailx-modal-btn">Try anyway</button></div>
|
|
1873
2151
|
</div>`;
|
|
1874
2152
|
document.getElementById("mv-render-anyway")?.addEventListener("click", () => {
|
|
@@ -1901,7 +2179,7 @@ async function showMessage(accountId, uid, folderId, specialUse, isRetry = false
|
|
|
1901
2179
|
const previewText = (msg.preview || cached?.preview || "").trim();
|
|
1902
2180
|
const waitStart = Date.now();
|
|
1903
2181
|
const indicatorHtml = `<span class="mv-wait-elapsed" data-start="${waitStart}">(0s)</span>`;
|
|
1904
|
-
bodyEl.innerHTML = previewText ? `<div class="mv-preview-placeholder">${
|
|
2182
|
+
bodyEl.innerHTML = previewText ? `<div class="mv-preview-placeholder">${escapeHtml3(previewText)}<div class="mv-tear-line" aria-label="snippet ends here, full message loading"><span>\u2702 snippet \u2014 fetching full message \u2702</span></div><div class="mv-wait-line">Fetching body from server\u2026 ${indicatorHtml}</div></div>` : `<div class="mv-empty">Fetching body from server\u2026 ${indicatorHtml}</div>`;
|
|
1905
2183
|
const captureGen = gen;
|
|
1906
2184
|
const tick = setInterval(() => {
|
|
1907
2185
|
if (captureGen !== showMessageGeneration) {
|
|
@@ -1945,7 +2223,7 @@ async function showMessage(accountId, uid, folderId, specialUse, isRetry = false
|
|
|
1945
2223
|
bodyEl.innerHTML = `<div class="mv-system-message mv-system-error">
|
|
1946
2224
|
<div class="mv-system-tag">mailx</div>
|
|
1947
2225
|
<div class="mv-system-title">Body fetch failed</div>
|
|
1948
|
-
<div class="mv-system-body">${
|
|
2226
|
+
<div class="mv-system-body">${escapeHtml3(msg.bodyError)}<br><span style="color:var(--color-text-muted);font-size:0.9em">${transient ? "Reopen this message to retry." : "The server reports this message no longer exists (deleted or moved by another client)."}</span></div>
|
|
1949
2227
|
</div>`;
|
|
1950
2228
|
currentMessage = msg;
|
|
1951
2229
|
currentAccountId = accountId;
|
|
@@ -2430,7 +2708,7 @@ async function showMessage(accountId, uid, folderId, specialUse, isRetry = false
|
|
|
2430
2708
|
<div class="mv-system-title">Showing the first ${mb(trunc.sentBytes)} of a ${mb(trunc.totalBytes)} message</div>
|
|
2431
2709
|
<div class="mv-system-body">This message has no MIME structure, so its entire payload is one plain-text part; the remainder is almost always the base64 of a returned attachment.
|
|
2432
2710
|
The complete message is on disk:<br>
|
|
2433
|
-
<code style="user-select:all;font-size:0.9em">${
|
|
2711
|
+
<code style="user-select:all;font-size:0.9em">${escapeHtml3(trunc.emlPath || "(path unavailable)")}</code></div>`;
|
|
2434
2712
|
bodyEl.appendChild(note);
|
|
2435
2713
|
}
|
|
2436
2714
|
bodyEl.appendChild(pre);
|
|
@@ -2440,7 +2718,7 @@ async function showMessage(accountId, uid, folderId, specialUse, isRetry = false
|
|
|
2440
2718
|
bodyEl.innerHTML = `<div class="mv-system-message mv-system-error">
|
|
2441
2719
|
<div class="mv-system-tag">mailx</div>
|
|
2442
2720
|
<div class="mv-system-title">Body fetch failed</div>
|
|
2443
|
-
<div class="mv-system-body">${
|
|
2721
|
+
<div class="mv-system-body">${escapeHtml3(fetchErr.error)}<br><span style="color:var(--color-text-muted);font-size:0.9em">Recorded ${Math.round((Date.now() - fetchErr.when) / 1e3)}s ago. ${fetchErr.transient ? "Will retry automatically." : "Permanent \u2014 server-side delete may have raced."}</span></div>
|
|
2444
2722
|
</div>`;
|
|
2445
2723
|
} else {
|
|
2446
2724
|
const emlPath = msg.emlPath || "";
|
|
@@ -2449,8 +2727,8 @@ async function showMessage(accountId, uid, folderId, specialUse, isRetry = false
|
|
|
2449
2727
|
const headline = attCount > 0 ? "This message has attachments but no body text." : "This message has no body text \u2014 only a subject.";
|
|
2450
2728
|
const crumbs = `mailx${appVer ? " " + appVer : ""} \xB7 ${accountId}/${uid}${emlPath ? " \xB7 " + emlPath : ""}`;
|
|
2451
2729
|
bodyEl.innerHTML = `<div class="mv-system-message">
|
|
2452
|
-
<div class="mv-system-body">${
|
|
2453
|
-
<div class="mv-system-body" style="color:var(--color-text-muted);font-size:0.8em;margin-top:8px">${
|
|
2730
|
+
<div class="mv-system-body">${escapeHtml3(headline)}</div>
|
|
2731
|
+
<div class="mv-system-body" style="color:var(--color-text-muted);font-size:0.8em;margin-top:8px">${escapeHtml3(crumbs)}</div>
|
|
2454
2732
|
</div>`;
|
|
2455
2733
|
}
|
|
2456
2734
|
}
|
|
@@ -2682,7 +2960,7 @@ function renderHeaderFromEnvelope(headerEl, env) {
|
|
|
2682
2960
|
}
|
|
2683
2961
|
}
|
|
2684
2962
|
}
|
|
2685
|
-
function
|
|
2963
|
+
function escapeHtml3(s) {
|
|
2686
2964
|
return (s || "").replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]);
|
|
2687
2965
|
}
|
|
2688
2966
|
function renderTextProgressively(pre, text, gen) {
|
|
@@ -4831,7 +5109,7 @@ function formatDate(epochMs) {
|
|
|
4831
5109
|
return d.toLocaleString(void 0, dateFmtSameYear);
|
|
4832
5110
|
return d.toLocaleString(void 0, dateFmt);
|
|
4833
5111
|
}
|
|
4834
|
-
function
|
|
5112
|
+
function escapeHtml4(s) {
|
|
4835
5113
|
const div = document.createElement("div");
|
|
4836
5114
|
div.textContent = s;
|
|
4837
5115
|
return div.innerHTML;
|
|
@@ -4984,7 +5262,7 @@ var init_message_list = __esm({
|
|
|
4984
5262
|
}
|
|
4985
5263
|
const subject = document.createElement("span");
|
|
4986
5264
|
subject.className = "ml-subject";
|
|
4987
|
-
subject.innerHTML =
|
|
5265
|
+
subject.innerHTML = escapeHtml4(msg.subject);
|
|
4988
5266
|
if (threadHead && threadCount > 1 && msg.threadId) {
|
|
4989
5267
|
const threadPill = document.createElement("span");
|
|
4990
5268
|
threadPill.className = "ml-thread-pill";
|
|
@@ -5400,7 +5678,7 @@ var init_message_list = __esm({
|
|
|
5400
5678
|
}
|
|
5401
5679
|
}
|
|
5402
5680
|
];
|
|
5403
|
-
showContextMenu(e.clientX, e.clientY, items);
|
|
5681
|
+
showContextMenu(e.clientX, e.clientY, items, { editTarget: e.target });
|
|
5404
5682
|
}
|
|
5405
5683
|
};
|
|
5406
5684
|
onEvent((ev) => {
|
|
@@ -5464,19 +5742,19 @@ async function openOutboxView() {
|
|
|
5464
5742
|
return `
|
|
5465
5743
|
<div class="ob-row ob-pink" data-idx="${i}">
|
|
5466
5744
|
<div class="ob-row-hdr">
|
|
5467
|
-
<span class="ob-acct">${
|
|
5468
|
-
<span class="ob-subject">${
|
|
5745
|
+
<span class="ob-acct">${escapeHtml5(m.accountId)}</span>
|
|
5746
|
+
<span class="ob-subject">${escapeHtml5(m.subject || "(no subject)")}</span>
|
|
5469
5747
|
<span class="ob-created">${fmtDate(m.createdAt)}</span>
|
|
5470
5748
|
${claimBadge}
|
|
5471
5749
|
${m.attempts > 0 ? `<span class="ob-badge ob-retry" title="Retry attempts made so far">retry \xD7${m.attempts}</span>` : ""}
|
|
5472
5750
|
</div>
|
|
5473
5751
|
<div class="ob-row-meta">
|
|
5474
|
-
<span class="ob-from">${
|
|
5475
|
-
\u2192 <span class="ob-to">${
|
|
5476
|
-
${m.cc ? ` \xB7 Cc: ${
|
|
5752
|
+
<span class="ob-from">${escapeHtml5(m.from || "")}</span>
|
|
5753
|
+
\u2192 <span class="ob-to">${escapeHtml5(m.to || "")}</span>
|
|
5754
|
+
${m.cc ? ` \xB7 Cc: ${escapeHtml5(m.cc)}` : ""}
|
|
5477
5755
|
<span class="ob-size">\xB7 ${(m.sizeBytes / 1024).toFixed(1)}kB</span>
|
|
5478
5756
|
</div>
|
|
5479
|
-
<div class="ob-row-path">${
|
|
5757
|
+
<div class="ob-row-path">${escapeHtml5(m.path)}</div>
|
|
5480
5758
|
<div class="ob-row-actions">
|
|
5481
5759
|
<button type="button" class="ob-cancel">Cancel</button>
|
|
5482
5760
|
</div>
|
|
@@ -5512,7 +5790,7 @@ Subject: ${m.subject}`;
|
|
|
5512
5790
|
const items = await listQueuedOutgoing();
|
|
5513
5791
|
renderList(items || []);
|
|
5514
5792
|
} catch (e) {
|
|
5515
|
-
listEl.innerHTML = `<div class="ob-empty">Load failed: ${
|
|
5793
|
+
listEl.innerHTML = `<div class="ob-empty">Load failed: ${escapeHtml5(e?.message || String(e))}</div>`;
|
|
5516
5794
|
}
|
|
5517
5795
|
};
|
|
5518
5796
|
const close = () => {
|
|
@@ -5537,7 +5815,7 @@ Subject: ${m.subject}`;
|
|
|
5537
5815
|
});
|
|
5538
5816
|
await reload();
|
|
5539
5817
|
}
|
|
5540
|
-
function
|
|
5818
|
+
function escapeHtml5(s) {
|
|
5541
5819
|
return s.replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]);
|
|
5542
5820
|
}
|
|
5543
5821
|
var isOpen2;
|
|
@@ -5576,7 +5854,7 @@ function calendarKind(id, primary) {
|
|
|
5576
5854
|
}
|
|
5577
5855
|
function calIconHtml(info) {
|
|
5578
5856
|
const kind = calendarKind(info.id, info.primary);
|
|
5579
|
-
const t =
|
|
5857
|
+
const t = escapeHtml6(info.name);
|
|
5580
5858
|
if (kind === "personal")
|
|
5581
5859
|
return `<span class="cal-ico cal-ico-dot" title="${t}"></span>`;
|
|
5582
5860
|
if (kind === "usHoliday")
|
|
@@ -5587,9 +5865,9 @@ function calIconHtml(info) {
|
|
|
5587
5865
|
return `<span class="cal-ico cal-ico-emoji" title="${t}">\u{1F382}</span>`;
|
|
5588
5866
|
if (kind === "otherHoliday")
|
|
5589
5867
|
return `<span class="cal-ico cal-ico-emoji" title="${t}">\u2726</span>`;
|
|
5590
|
-
const letter =
|
|
5868
|
+
const letter = escapeHtml6((info.name.trim()[0] || "?").toUpperCase());
|
|
5591
5869
|
const color = info.color || "#7a7a7a";
|
|
5592
|
-
return `<span class="cal-ico cal-ico-mono" style="background:${
|
|
5870
|
+
return `<span class="cal-ico cal-ico-mono" style="background:${escapeHtml6(color)}" title="${t}">${letter}</span>`;
|
|
5593
5871
|
}
|
|
5594
5872
|
function calInfoFor(calendarId) {
|
|
5595
5873
|
const id = calendarId || "primary";
|
|
@@ -5638,10 +5916,10 @@ async function renderCalendarList() {
|
|
|
5638
5916
|
host.innerHTML = "";
|
|
5639
5917
|
} else {
|
|
5640
5918
|
const sorted = [...list].sort((a, b) => (a.primary ? 0 : 1) - (b.primary ? 0 : 1) || a.name.localeCompare(b.name));
|
|
5641
|
-
host.innerHTML = sorted.map((c) => `<label class="cal-side-cal-row" title="${
|
|
5642
|
-
<input type="checkbox" class="cal-side-cal-check" data-cal-id="${
|
|
5919
|
+
host.innerHTML = sorted.map((c) => `<label class="cal-side-cal-row" title="${escapeHtml6(c.name)}">
|
|
5920
|
+
<input type="checkbox" class="cal-side-cal-check" data-cal-id="${escapeHtml6(c.id)}" ${hiddenCalendars.has(c.id) ? "" : "checked"}>
|
|
5643
5921
|
${calIconHtml(c)}
|
|
5644
|
-
<span class="cal-side-cal-name">${
|
|
5922
|
+
<span class="cal-side-cal-name">${escapeHtml6(c.name)}</span>
|
|
5645
5923
|
</label>`).join("");
|
|
5646
5924
|
host.querySelectorAll(".cal-side-cal-check").forEach((cb) => {
|
|
5647
5925
|
cb.addEventListener("change", async () => {
|
|
@@ -5728,7 +6006,7 @@ function formatTime(e) {
|
|
|
5728
6006
|
return "all day";
|
|
5729
6007
|
return new Date(e.start).toLocaleTimeString(void 0, { hour: "2-digit", minute: "2-digit", hour12: false });
|
|
5730
6008
|
}
|
|
5731
|
-
function
|
|
6009
|
+
function escapeHtml6(s) {
|
|
5732
6010
|
return s.replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]);
|
|
5733
6011
|
}
|
|
5734
6012
|
function renderHead() {
|
|
@@ -5745,10 +6023,10 @@ function overdueTaskRowHtml(t) {
|
|
|
5745
6023
|
const sameYear = d.getFullYear() === (/* @__PURE__ */ new Date()).getFullYear();
|
|
5746
6024
|
dueLabel = sameYear ? `${d.getMonth() + 1}/${d.getDate()}` : `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
|
|
5747
6025
|
}
|
|
5748
|
-
return `<div class="cal-side-task cal-side-task-overdue" data-uuid="${
|
|
6026
|
+
return `<div class="cal-side-task cal-side-task-overdue" data-uuid="${escapeHtml6(t.uuid)}">
|
|
5749
6027
|
<input type="checkbox" class="cal-side-overdue-check" title="Mark done">
|
|
5750
|
-
<span class="cal-side-task-title" title="${
|
|
5751
|
-
<span class="cal-side-task-due overdue">${
|
|
6028
|
+
<span class="cal-side-task-title" title="${escapeHtml6(t.title)}">${escapeHtml6(t.title)}</span>
|
|
6029
|
+
<span class="cal-side-task-due overdue">${escapeHtml6(dueLabel)}</span>
|
|
5752
6030
|
</div>`;
|
|
5753
6031
|
}
|
|
5754
6032
|
function renderEvents(events) {
|
|
@@ -5805,10 +6083,10 @@ function renderEvents(events) {
|
|
|
5805
6083
|
html += `<div class="cal-side-day cal-side-day-daily">Daily</div>`;
|
|
5806
6084
|
for (const e of dailyHeads) {
|
|
5807
6085
|
const link = e.htmlLink || "";
|
|
5808
|
-
html += `<div class="cal-side-event" data-id="${e.id}" data-link="${
|
|
6086
|
+
html += `<div class="cal-side-event" data-id="${e.id}" data-link="${escapeHtml6(link)}" ${link ? 'title="Click to open in Google Calendar"' : ""}>
|
|
5809
6087
|
${calIconHtml(calInfoFor(e.calendarId))}
|
|
5810
|
-
<span class="cal-side-event-time">${
|
|
5811
|
-
<span class="cal-side-event-title" title="${
|
|
6088
|
+
<span class="cal-side-event-time">${escapeHtml6(formatTime(e))}</span>
|
|
6089
|
+
<span class="cal-side-event-title" title="${escapeHtml6(e.title)}">${escapeHtml6(e.title)}<span class="cal-side-event-recur" title="Daily">\u21BB</span></span>
|
|
5812
6090
|
</div>`;
|
|
5813
6091
|
}
|
|
5814
6092
|
}
|
|
@@ -5826,7 +6104,7 @@ function renderEvents(events) {
|
|
|
5826
6104
|
const d = new Date(e.start);
|
|
5827
6105
|
const dayKey = `${d.getFullYear()}-${d.getMonth()}-${d.getDate()}`;
|
|
5828
6106
|
if (dayKey !== lastDayKey) {
|
|
5829
|
-
html += `<div class="cal-side-day">${
|
|
6107
|
+
html += `<div class="cal-side-day">${escapeHtml6(formatDayHeader(d, today, tomorrow))}</div>`;
|
|
5830
6108
|
lastDayKey = dayKey;
|
|
5831
6109
|
}
|
|
5832
6110
|
const recurMark = e.recurringEventId ? `<span class="cal-side-event-recur" title="Recurring event">\u21BB</span>` : "";
|
|
@@ -5834,14 +6112,14 @@ function renderEvents(events) {
|
|
|
5834
6112
|
const recurAttr = e.recurringEventId ? ' data-recurring="1"' : "";
|
|
5835
6113
|
if (isHolidayKind || kind === "birthday") {
|
|
5836
6114
|
html += `<div class="cal-side-event" data-holiday="1" data-holiday-kind="${kind}" data-id="${e.id}">
|
|
5837
|
-
<span class="cal-side-event-title cal-side-event-holiday-title" title="${
|
|
6115
|
+
<span class="cal-side-event-title cal-side-event-holiday-title" title="${escapeHtml6(e.title)}">${calIconHtml(info)} ${escapeHtml6(e.title)}</span>
|
|
5838
6116
|
</div>`;
|
|
5839
6117
|
} else {
|
|
5840
6118
|
const titleAttr = link ? 'title="Click to open in Google Calendar"' : "";
|
|
5841
|
-
html += `<div class="cal-side-event" data-id="${e.id}"${recurAttr} data-link="${
|
|
6119
|
+
html += `<div class="cal-side-event" data-id="${e.id}"${recurAttr} data-link="${escapeHtml6(link)}" ${titleAttr}>
|
|
5842
6120
|
${calIconHtml(info)}
|
|
5843
|
-
<span class="cal-side-event-time">${
|
|
5844
|
-
<span class="cal-side-event-title" title="${
|
|
6121
|
+
<span class="cal-side-event-time">${escapeHtml6(formatTime(e))}</span>
|
|
6122
|
+
<span class="cal-side-event-title" title="${escapeHtml6(e.title)}">${escapeHtml6(e.title)}${recurMark}</span>
|
|
5845
6123
|
</div>`;
|
|
5846
6124
|
}
|
|
5847
6125
|
}
|
|
@@ -5893,7 +6171,7 @@ function renderEvents(events) {
|
|
|
5893
6171
|
action: () => openInBrowser("https://calendar.google.com/")
|
|
5894
6172
|
});
|
|
5895
6173
|
if (items.length > 0)
|
|
5896
|
-
showContextMenu(e.clientX, e.clientY, items);
|
|
6174
|
+
showContextMenu(e.clientX, e.clientY, items, { editTarget: e.target });
|
|
5897
6175
|
});
|
|
5898
6176
|
});
|
|
5899
6177
|
}
|
|
@@ -5926,7 +6204,7 @@ async function renderTasks(prefetched) {
|
|
|
5926
6204
|
const sel = selectedTaskUuids.has(t.uuid) ? " selected" : "";
|
|
5927
6205
|
html += `<div class="cal-side-task${sel}" data-uuid="${t.uuid}">
|
|
5928
6206
|
<input type="checkbox" ${done ? "checked" : ""} class="cal-side-task-check">
|
|
5929
|
-
<span class="cal-side-task-title${done ? " done" : ""}" title="${
|
|
6207
|
+
<span class="cal-side-task-title${done ? " done" : ""}" title="${escapeHtml6(t.title)}">${escapeHtml6(t.title)}</span>
|
|
5930
6208
|
${dueHtml}
|
|
5931
6209
|
<button class="cal-side-task-delete" title="Delete task" aria-label="Delete task">\xD7</button>
|
|
5932
6210
|
</div>`;
|
|
@@ -6011,7 +6289,7 @@ async function refresh() {
|
|
|
6011
6289
|
} catch (e) {
|
|
6012
6290
|
const body = document.getElementById("cal-side-body");
|
|
6013
6291
|
if (body)
|
|
6014
|
-
body.innerHTML = `<div class="cal-side-empty cal-side-quota-error">Couldn't load calendar: ${
|
|
6292
|
+
body.innerHTML = `<div class="cal-side-empty cal-side-quota-error">Couldn't load calendar: ${escapeHtml6(e?.message || String(e))}</div>`;
|
|
6015
6293
|
}
|
|
6016
6294
|
renderTasks(prefetchedTasks);
|
|
6017
6295
|
}
|
|
@@ -6308,14 +6586,14 @@ function initCalendarSidebar() {
|
|
|
6308
6586
|
const host = event.feature === "tasks" ? document.getElementById("cal-side-tasks") : document.getElementById("cal-side-body");
|
|
6309
6587
|
if (host && !host.querySelector(".cal-side-quota-error")) {
|
|
6310
6588
|
const msg = event.message || `Google ${event.feature} quota exceeded \u2014 try again later.`;
|
|
6311
|
-
host.innerHTML = `<div class="cal-side-empty cal-side-quota-error">${
|
|
6589
|
+
host.innerHTML = `<div class="cal-side-empty cal-side-quota-error">${escapeHtml6(msg)}</div>`;
|
|
6312
6590
|
}
|
|
6313
6591
|
} else if (event?.type === "authScopeError") {
|
|
6314
6592
|
const host = event.feature === "tasks" ? document.getElementById("cal-side-tasks") : document.getElementById("cal-side-body");
|
|
6315
6593
|
if (host && !host.querySelector(".cal-side-auth-error")) {
|
|
6316
6594
|
const msg = event.message || "Google access needs re-consent.";
|
|
6317
6595
|
host.innerHTML = `<div class="cal-side-empty cal-side-auth-error">
|
|
6318
|
-
<div style="margin-bottom:0.6em">${
|
|
6596
|
+
<div style="margin-bottom:0.6em">${escapeHtml6(msg)}</div>
|
|
6319
6597
|
<button type="button" class="cal-side-reauth-btn" style="padding:0.3em 0.8em;border-radius:4px;border:1px solid currentColor;background:transparent;color:inherit;cursor:pointer;font-size:0.9em">Re-authenticate Now</button>
|
|
6320
6598
|
</div>`;
|
|
6321
6599
|
const btn = host.querySelector(".cal-side-reauth-btn");
|
|
@@ -6623,7 +6901,7 @@ function retractSuppressedPopups() {
|
|
|
6623
6901
|
}
|
|
6624
6902
|
}
|
|
6625
6903
|
}
|
|
6626
|
-
function
|
|
6904
|
+
function escapeHtml7(s) {
|
|
6627
6905
|
return s.replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]);
|
|
6628
6906
|
}
|
|
6629
6907
|
function showInWebViewPopup(opts, onRegisterClose) {
|
|
@@ -6632,11 +6910,11 @@ function showInWebViewPopup(opts, onRegisterClose) {
|
|
|
6632
6910
|
overlay.className = "alarm-overlay";
|
|
6633
6911
|
const panel = document.createElement("div");
|
|
6634
6912
|
panel.className = "alarm-panel";
|
|
6635
|
-
const buttonsHtml = opts.buttons.map((b) => `<button type="button" class="alarm-btn${b === "Open" || b === "Dismiss" ? " alarm-btn-primary" : ""}" data-button="${
|
|
6913
|
+
const buttonsHtml = opts.buttons.map((b) => `<button type="button" class="alarm-btn${b === "Open" || b === "Dismiss" ? " alarm-btn-primary" : ""}" data-button="${escapeHtml7(b)}">${escapeHtml7(b)}</button>`).join("");
|
|
6636
6914
|
panel.innerHTML = `
|
|
6637
6915
|
<div class="alarm-head">
|
|
6638
6916
|
<span class="alarm-icon">\u23F0</span>
|
|
6639
|
-
<span class="alarm-title">${
|
|
6917
|
+
<span class="alarm-title">${escapeHtml7(opts.title)}</span>
|
|
6640
6918
|
<button type="button" class="alarm-close" data-button="" aria-label="Close">×</button>
|
|
6641
6919
|
</div>
|
|
6642
6920
|
<div class="alarm-body">${opts.html}</div>
|
|
@@ -6695,7 +6973,7 @@ async function firePopupForItem(item) {
|
|
|
6695
6973
|
const actionBtns = ["Dismiss", "Open"];
|
|
6696
6974
|
if (item.kind === "calendar")
|
|
6697
6975
|
actionBtns.push("Delete");
|
|
6698
|
-
const actionHtml = actionBtns.map((b) => `<button type="button" class="action" data-btn="${
|
|
6976
|
+
const actionHtml = actionBtns.map((b) => `<button type="button" class="action" data-btn="${escapeHtml7(b)}">${escapeHtml7(b)}</button>`).join("");
|
|
6699
6977
|
const html = `<!DOCTYPE html>
|
|
6700
6978
|
<html><head><meta charset="utf-8"><style>
|
|
6701
6979
|
html, body { height: 100%; }
|
|
@@ -6721,8 +6999,8 @@ async function firePopupForItem(item) {
|
|
|
6721
6999
|
.actions button.action[data-btn="Delete"] { background: #b00; }
|
|
6722
7000
|
.actions button.action[data-btn="Delete"]:hover { background: #800; }
|
|
6723
7001
|
</style></head><body>
|
|
6724
|
-
<div class="title"><span class="icon">${icon}</span>${
|
|
6725
|
-
<div class="when">${
|
|
7002
|
+
<div class="title"><span class="icon">${icon}</span>${escapeHtml7(item.title)}</div>
|
|
7003
|
+
<div class="when">${escapeHtml7(formatWhen(item.whenMs))}</div>
|
|
6726
7004
|
<div class="kind">${kindLabel}</div>
|
|
6727
7005
|
<div class="row">
|
|
6728
7006
|
<span class="row-label">Snooze:</span>
|
|
@@ -7564,7 +7842,7 @@ function renderNode(node, container, depth) {
|
|
|
7564
7842
|
}
|
|
7565
7843
|
} });
|
|
7566
7844
|
}
|
|
7567
|
-
showContextMenu(e.clientX, e.clientY, items);
|
|
7845
|
+
showContextMenu(e.clientX, e.clientY, items, { editTarget: e.target });
|
|
7568
7846
|
});
|
|
7569
7847
|
if (node.id !== -1) {
|
|
7570
7848
|
let dragExpandTimer = null;
|
|
@@ -8135,7 +8413,7 @@ async function loadFolderTree(container) {
|
|
|
8135
8413
|
}
|
|
8136
8414
|
} }
|
|
8137
8415
|
];
|
|
8138
|
-
showContextMenu(e.clientX, e.clientY, items);
|
|
8416
|
+
showContextMenu(e.clientX, e.clientY, items, { editTarget: e.target });
|
|
8139
8417
|
});
|
|
8140
8418
|
accountEl.appendChild(header);
|
|
8141
8419
|
if (accountExpanded && folders.length > 0) {
|
|
@@ -8359,6 +8637,8 @@ init_mailx_types();
|
|
|
8359
8637
|
init_message_viewer();
|
|
8360
8638
|
init_api_client();
|
|
8361
8639
|
init_message_state();
|
|
8640
|
+
init_edit_menu();
|
|
8641
|
+
init_context_menu();
|
|
8362
8642
|
installConsoleCapture();
|
|
8363
8643
|
(function installStallWatchdog() {
|
|
8364
8644
|
const EXPECTED_MS = 1e3;
|
|
@@ -8418,7 +8698,10 @@ window.__btick && window.__btick("app.ts module body executing");
|
|
|
8418
8698
|
}
|
|
8419
8699
|
}, true);
|
|
8420
8700
|
document.addEventListener("contextmenu", (e) => {
|
|
8421
|
-
if (
|
|
8701
|
+
if (e.defaultPrevented) return;
|
|
8702
|
+
e.preventDefault();
|
|
8703
|
+
const items = editMenuItems({ target: e.target });
|
|
8704
|
+
if (items.length > 0) showContextMenu(e.clientX, e.clientY, items);
|
|
8422
8705
|
});
|
|
8423
8706
|
})();
|
|
8424
8707
|
var baseTitle = APP_NAME;
|
|
@@ -10096,11 +10379,11 @@ document.addEventListener("mailx-share-intent", ((e) => {
|
|
|
10096
10379
|
const bcc = sp.get("bcc");
|
|
10097
10380
|
if (bcc) init.bcc = bcc.split(",").map((s) => s.trim()).filter(Boolean);
|
|
10098
10381
|
} catch {
|
|
10099
|
-
init.bodyHtml = `<p>${
|
|
10382
|
+
init.bodyHtml = `<p>${escapeHtml8(detail.mailto)}</p>`;
|
|
10100
10383
|
}
|
|
10101
10384
|
} else {
|
|
10102
10385
|
if (detail.subject) init.subject = detail.subject;
|
|
10103
|
-
if (detail.text) init.bodyHtml = `<p>${
|
|
10386
|
+
if (detail.text) init.bodyHtml = `<p>${escapeHtml8(String(detail.text)).replace(/\n/g, "<br>")}</p>`;
|
|
10104
10387
|
}
|
|
10105
10388
|
if (Array.isArray(detail.attachments) && detail.attachments.length) {
|
|
10106
10389
|
init.attachments = detail.attachments.filter((a) => a?.filename && a?.dataBase64);
|
|
@@ -12423,13 +12706,13 @@ async function openAboutDialog() {
|
|
|
12423
12706
|
rows.push(["Window", `${window.innerWidth}\xD7${window.innerHeight}`]);
|
|
12424
12707
|
body.innerHTML = `
|
|
12425
12708
|
<dl class="mailx-about-dl">
|
|
12426
|
-
${rows.map(([k, val]) => `<dt>${k}</dt><dd>${k === "Version" ? val :
|
|
12709
|
+
${rows.map(([k, val]) => `<dt>${k}</dt><dd>${k === "Version" ? val : escapeHtml8(val)}</dd>`).join("")}
|
|
12427
12710
|
</dl>
|
|
12428
12711
|
${(accounts || []).length ? `
|
|
12429
12712
|
<div class="mailx-about-accounts">
|
|
12430
12713
|
<div class="mailx-about-section">Accounts</div>
|
|
12431
12714
|
<ul>
|
|
12432
|
-
${accounts.map((a) => `<li>${
|
|
12715
|
+
${accounts.map((a) => `<li>${escapeHtml8(a.email || a.id)}${a.name ? ` \u2014 ${escapeHtml8(a.name)}` : ""}</li>`).join("")}
|
|
12433
12716
|
</ul>
|
|
12434
12717
|
</div>` : ""}
|
|
12435
12718
|
<div class="mailx-about-foot">${APP_NAME} \u2014 local-first mail client</div>`;
|
|
@@ -12437,7 +12720,7 @@ async function openAboutDialog() {
|
|
|
12437
12720
|
body.textContent = `Failed to load: ${e.message}`;
|
|
12438
12721
|
}
|
|
12439
12722
|
}
|
|
12440
|
-
function
|
|
12723
|
+
function escapeHtml8(s) {
|
|
12441
12724
|
return String(s).replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]);
|
|
12442
12725
|
}
|
|
12443
12726
|
optThreaded?.addEventListener("change", () => {
|