mirror of
https://github.com/ghostersk/gowebmail.git
synced 2026-04-17 08:36:01 +01:00
fixed message rendering with html content ( white background)
This commit is contained in:
@@ -134,6 +134,10 @@ func TestConnection(account *gomailModels.EmailAccount) error {
|
||||
|
||||
func (c *Client) Close() { c.imap.Logout() }
|
||||
|
||||
func (c *Client) DeleteMailbox(name string) error {
|
||||
return c.imap.Delete(name)
|
||||
}
|
||||
|
||||
func (c *Client) ListMailboxes() ([]*imap.MailboxInfo, error) {
|
||||
ch := make(chan *imap.MailboxInfo, 64)
|
||||
done := make(chan error, 1)
|
||||
|
||||
@@ -404,6 +404,24 @@ func (h *APIHandler) CountFolderMessages(w http.ResponseWriter, r *http.Request)
|
||||
func (h *APIHandler) DeleteFolder(w http.ResponseWriter, r *http.Request) {
|
||||
userID := middleware.GetUserID(r)
|
||||
folderID := pathInt64(r, "id")
|
||||
|
||||
// Look up folder before deleting so we have its path and account
|
||||
folder, err := h.db.GetFolderByID(folderID)
|
||||
if err != nil || folder == nil {
|
||||
h.writeError(w, http.StatusNotFound, "folder not found")
|
||||
return
|
||||
}
|
||||
|
||||
// Delete on IMAP server first
|
||||
account, err := h.db.GetAccount(folder.AccountID)
|
||||
if err == nil && account != nil {
|
||||
if imapClient, cerr := email.Connect(context.Background(), account); cerr == nil {
|
||||
_ = imapClient.DeleteMailbox(folder.FullPath)
|
||||
imapClient.Close()
|
||||
}
|
||||
}
|
||||
|
||||
// Delete from local DB
|
||||
if err := h.db.DeleteFolder(folderID, userID); err != nil {
|
||||
h.writeError(w, http.StatusInternalServerError, "delete failed")
|
||||
return
|
||||
|
||||
@@ -421,7 +421,17 @@ body.admin-page{overflow:auto;background:var(--bg)}
|
||||
}
|
||||
.inline-confirm.open{opacity:1;pointer-events:all;transform:translate(-50%,-50%)}
|
||||
|
||||
/* ── Folder no-sync indicator ────────────────────────────────── */
|
||||
/* ── Context menu submenu ────────────────────────────────────── */
|
||||
.ctx-has-sub{position:relative;justify-content:space-between}
|
||||
.ctx-sub-arrow{margin-left:auto;font-size:12px;color:var(--muted);pointer-events:none}
|
||||
.ctx-submenu{
|
||||
display:none;position:absolute;left:100%;top:-4px;
|
||||
background:var(--surface2);border:1px solid var(--border2);
|
||||
border-radius:8px;padding:4px;min-width:160px;
|
||||
box-shadow:0 8px 28px rgba(0,0,0,.55);z-index:210;
|
||||
}
|
||||
.ctx-has-sub:hover>.ctx-submenu{display:block}
|
||||
.ctx-sub-item{white-space:nowrap}
|
||||
.folder-nosync{opacity:.65}
|
||||
|
||||
/* ── Compose attach list ─────────────────────────────────────── */
|
||||
|
||||
@@ -195,9 +195,45 @@ async function openEditAccount(id) {
|
||||
connEl.style.display='none';
|
||||
errEl.style.display=r.last_error?'block':'none';
|
||||
if (r.last_error) errEl.textContent='Last sync error: '+r.last_error;
|
||||
|
||||
// Load hidden folders for this account
|
||||
const hiddenEl = document.getElementById('edit-hidden-folders');
|
||||
const hidden = S.folders.filter(f=>f.account_id===id && f.is_hidden);
|
||||
if (!hidden.length) {
|
||||
hiddenEl.innerHTML='<span style="color:var(--muted);font-size:12px">No hidden folders.</span>';
|
||||
} else {
|
||||
hiddenEl.innerHTML = hidden.map(f=>`
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;padding:5px 0;border-bottom:1px solid var(--border)">
|
||||
<span style="font-size:13px">${esc(f.name)}</span>
|
||||
<button class="btn-secondary" style="font-size:11px;padding:3px 10px" onclick="unhideFolder(${f.id})">Unhide</button>
|
||||
</div>`).join('');
|
||||
}
|
||||
|
||||
openModal('edit-account-modal');
|
||||
}
|
||||
|
||||
async function unhideFolder(folderId) {
|
||||
const f = S.folders.find(f=>f.id===folderId);
|
||||
if (!f) return;
|
||||
const r = await api('PUT','/folders/'+folderId+'/visibility',{is_hidden:false, sync_enabled:true});
|
||||
if (r?.ok) {
|
||||
toast('Folder restored to sidebar','success');
|
||||
await loadFolders();
|
||||
// Refresh hidden list in modal
|
||||
const accId = parseInt(document.getElementById('edit-account-id').value);
|
||||
if (accId) {
|
||||
const hiddenEl = document.getElementById('edit-hidden-folders');
|
||||
const hidden = S.folders.filter(f=>f.account_id===accId && f.is_hidden);
|
||||
if (!hidden.length) hiddenEl.innerHTML='<span style="color:var(--muted);font-size:12px">No hidden folders.</span>';
|
||||
else hiddenEl.innerHTML = hidden.map(f=>`
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;padding:5px 0;border-bottom:1px solid var(--border)">
|
||||
<span style="font-size:13px">${esc(f.name)}</span>
|
||||
<button class="btn-secondary" style="font-size:11px;padding:3px 10px" onclick="unhideFolder(${f.id})">Unhide</button>
|
||||
</div>`).join('');
|
||||
}
|
||||
} else toast('Failed to unhide folder','error');
|
||||
}
|
||||
|
||||
function toggleSyncDaysField() {
|
||||
const mode=document.getElementById('edit-sync-mode')?.value;
|
||||
const row=document.getElementById('edit-sync-days-row');
|
||||
@@ -313,13 +349,20 @@ function showFolderMenu(e, folderId) {
|
||||
const f = S.folders.find(f=>f.id===folderId);
|
||||
if (!f) return;
|
||||
const syncLabel = f.sync_enabled ? '⊘ Disable sync' : '↻ Enable sync';
|
||||
const otherFolders = S.folders.filter(x=>x.id!==folderId&&x.account_id===f.account_id&&!x.is_hidden).slice(0,12);
|
||||
const moveSub = otherFolders.map(x=>`<div class="ctx-item" style="padding-left:22px" onclick="moveFolderContents(${folderId},${x.id});closeMenu()">${esc(x.name)}</div>`).join('');
|
||||
const otherFolders = S.folders.filter(x=>x.id!==folderId&&x.account_id===f.account_id&&!x.is_hidden).slice(0,16);
|
||||
const moveItems = otherFolders.map(x=>
|
||||
`<div class="ctx-item ctx-sub-item" onclick="moveFolderContents(${folderId},${x.id});closeMenu()">${esc(x.name)}</div>`
|
||||
).join('');
|
||||
const moveEntry = otherFolders.length ? `
|
||||
<div class="ctx-item ctx-has-sub">📂 Move messages to
|
||||
<span class="ctx-sub-arrow">›</span>
|
||||
<div class="ctx-submenu">${moveItems}</div>
|
||||
</div>` : '';
|
||||
showCtxMenu(e, `
|
||||
<div class="ctx-item" onclick="syncFolderNow(${folderId});closeMenu()">↻ Sync this folder</div>
|
||||
<div class="ctx-item" onclick="toggleFolderSync(${folderId});closeMenu()">${syncLabel}</div>
|
||||
<div class="ctx-sep"></div>
|
||||
${moveSub?`<div style="font-size:10px;color:var(--muted);padding:4px 12px;text-transform:uppercase;letter-spacing:.7px">Move messages to</div>${moveSub}<div class="ctx-sep"></div>`:''}
|
||||
${moveEntry}
|
||||
<div class="ctx-item" onclick="confirmHideFolder(${folderId});closeMenu()">👁 Hide from sidebar</div>
|
||||
<div class="ctx-item danger" onclick="confirmDeleteFolder(${folderId});closeMenu()">🗑 Delete folder</div>`);
|
||||
}
|
||||
@@ -513,20 +556,36 @@ function renderMessageDetail(msg, showRemoteContent) {
|
||||
const detail=document.getElementById('message-detail');
|
||||
const allowed=showRemoteContent||S.remoteWhitelist.has(msg.from_email);
|
||||
|
||||
// CSS injected into every iframe — forces white background so dark-themed emails
|
||||
// don't inherit our app's dark theme and become unreadable
|
||||
const cssReset = `<style>html,body{background:#ffffff!important;color:#1a1a1a!important;` +
|
||||
`font-family:Arial,sans-serif;font-size:14px;line-height:1.5;margin:8px}a{color:#1a5fb4}</style>`;
|
||||
|
||||
let bodyHtml='';
|
||||
if (msg.body_html) {
|
||||
if (allowed) {
|
||||
const srcdoc = cssReset + msg.body_html;
|
||||
bodyHtml=`<iframe id="msg-frame" sandbox="allow-same-origin allow-popups"
|
||||
style="width:100%;border:none;min-height:300px;display:block"
|
||||
srcdoc="${msg.body_html.replace(/"/g,'"')}"></iframe>`;
|
||||
srcdoc="${srcdoc.replace(/"/g,'"')}"></iframe>`;
|
||||
} else {
|
||||
// Strip only remote resources (img src, background-image urls, external link/script)
|
||||
// Keep full HTML structure so text remains readable
|
||||
const stripped = msg.body_html
|
||||
.replace(/<img(\s[^>]*?)src\s*=\s*(['"])[^'"]*\2/gi, '<img$1src=""data-blocked="1"')
|
||||
.replace(/url\s*\(\s*(['"]?)https?:\/\/[^)'"]+\1\s*\)/gi, 'url()')
|
||||
.replace(/<link[^>]*>/gi, '')
|
||||
.replace(/<script[\s\S]*?<\/script>/gi, '');
|
||||
const srcdoc = cssReset + stripped;
|
||||
bodyHtml=`<div class="remote-content-banner">
|
||||
<svg viewBox="0 0 24 24" width="14" height="14" fill="currentColor"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z"/></svg>
|
||||
<svg viewBox="0 0 24 24" width="14" height="14" fill="currentColor"><path d="M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z"/></svg>
|
||||
Remote images blocked.
|
||||
<button class="rcb-btn" onclick="renderMessageDetail(S.currentMessage,true)">Load content</button>
|
||||
<button class="rcb-btn" onclick="renderMessageDetail(S.currentMessage,true)">Load images</button>
|
||||
<button class="rcb-btn" onclick="whitelistSender('${esc(msg.from_email)}')">Always allow from ${esc(msg.from_email)}</button>
|
||||
</div>
|
||||
<div class="detail-body-text">${esc(msg.body_text||'(empty)')}</div>`;
|
||||
<iframe id="msg-frame" sandbox="allow-same-origin allow-popups"
|
||||
style="width:100%;border:none;min-height:300px;display:block"
|
||||
srcdoc="${srcdoc.replace(/"/g,'"')}"></iframe>`;
|
||||
}
|
||||
} else {
|
||||
bodyHtml=`<div class="detail-body-text">${esc(msg.body_text||'(empty)')}</div>`;
|
||||
|
||||
@@ -245,6 +245,8 @@
|
||||
</div>
|
||||
<div id="edit-conn-result" class="test-result" style="display:none"></div>
|
||||
<div id="edit-last-error" style="display:none" class="alert error"></div>
|
||||
<div class="settings-group-title" style="margin:16px 0 8px">Hidden Folders</div>
|
||||
<div id="edit-hidden-folders" style="font-size:12px;color:var(--muted)">Loading…</div>
|
||||
<div class="modal-actions">
|
||||
<button class="modal-cancel" onclick="closeModal('edit-account-modal')">Cancel</button>
|
||||
<button class="btn-secondary" id="edit-test-btn" onclick="testEditConnection()">Test Connection</button>
|
||||
@@ -300,5 +302,5 @@
|
||||
{{end}}
|
||||
|
||||
{{define "scripts"}}
|
||||
<script src="/static/js/app.js?v=7"></script>
|
||||
<script src="/static/js/app.js?v=9"></script>
|
||||
{{end}}
|
||||
|
||||
@@ -5,12 +5,12 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{block "title" .}}GoMail{{end}}</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=DM+Serif+Display&family=DM+Sans:ital,wght@0,300;0,400;0,500;1,400&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="/static/css/gomail.css?v=7">
|
||||
<link rel="stylesheet" href="/static/css/gomail.css?v=9">
|
||||
{{block "head_extra" .}}{{end}}
|
||||
</head>
|
||||
<body class="{{block "body_class" .}}{{end}}">
|
||||
{{block "body" .}}{{end}}
|
||||
<script src="/static/js/gomail.js?v=7"></script>
|
||||
<script src="/static/js/gomail.js?v=9"></script>
|
||||
{{block "scripts" .}}{{end}}
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user