Эх сурвалжийг харах

fix: render inbound HTML messages

Render inbound HTML bodies in a sandboxed preview while keeping source and raw MIME tabs available for diagnostics.

AI-Co-Authored-By: Codex
chendeben 1 сар өмнө
parent
commit
122b291ec4
42 өөрчлөгдсөн 95 нэмэгдсэн , 33 устгасан
  1. 0 0
      public/assets/Admin-CHGgcpnY.js
  2. 0 0
      public/assets/ApiTokens-BoMuWYkK.js
  3. 1 1
      public/assets/CodeBlock-B-NIgQUI.js
  4. 1 1
      public/assets/CopyOutlined-CjLq_nuP.js
  5. 0 1
      public/assets/Dashboard-VPvfYTM4.js
  6. 2 2
      public/assets/DashboardCharts-s26_Bnz4.js
  7. 0 0
      public/assets/DnsApi-BFsXA3-6.js
  8. 0 0
      public/assets/DomainDetail-C_ScJIVm.js
  9. 0 0
      public/assets/Domains-ZaPKfS2a.js
  10. 1 1
      public/assets/EditOutlined-BjvG9r8B.js
  11. 1 1
      public/assets/EllipsisOutlined-Bk3Ymyne.js
  12. 0 0
      public/assets/Inbox-BGre424Y.js
  13. 0 0
      public/assets/Inbox-xZSCNUoB.js
  14. 1 1
      public/assets/PlusOutlined-CILz_LKx.js
  15. 1 1
      public/assets/ReloadOutlined-Cq8HKCaL.js
  16. 1 1
      public/assets/SearchOutlined-Bw_KR-cI.js
  17. 0 0
      public/assets/SendingLogs-BkFSn1G1.js
  18. 0 0
      public/assets/Settings-DSNkOz-2.js
  19. 0 0
      public/assets/SmtpCredentials-O4rUapPO.js
  20. 0 0
      public/assets/StatusPill-Bcw_ne6L.js
  21. 1 1
      public/assets/StatusTag-CBp0RCE4.js
  22. 1 1
      public/assets/ThunderboltOutlined-CMOzejTs.js
  23. 0 0
      public/assets/Webhooks-C4qtVEsF.js
  24. 1 1
      public/assets/form-BRrMNJiX.js
  25. 1 1
      public/assets/grid-DcIFgt5N.js
  26. 0 1
      public/assets/index-CBPGvhVC.js
  27. 0 0
      public/assets/list-B3SBzx24.js
  28. 0 0
      public/assets/login-r6bTsl_n.js
  29. 1 1
      public/assets/modal-BNBPl4Qp.js
  30. 0 0
      public/assets/popconfirm-DHzKhZl9.js
  31. 1 0
      public/assets/row-Cmd9RpeX.js
  32. 0 1
      public/assets/row-cwQH2RQY.js
  33. 0 0
      public/assets/table-CHt5qwx3.js
  34. 0 0
      public/assets/theme-DNlqAiGO.js
  35. 0 0
      public/assets/theme-e1juCvr1.css
  36. 3 3
      public/index.html
  37. 5 5
      public/login.html
  38. 4 2
      src/frontend/i18n/index.js
  39. 10 0
      src/frontend/styles.css
  40. 32 4
      src/pages/Inbox.tsx
  41. 25 1
      test/ui/inbox-navigation.test.tsx
  42. 1 1
      test/ui/operations-navigation.test.tsx

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 0
public/assets/Admin-CHGgcpnY.js


Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 0
public/assets/ApiTokens-BoMuWYkK.js


+ 1 - 1
public/assets/CodeBlock-Bv1z-p9E.js → public/assets/CodeBlock-B-NIgQUI.js

@@ -1 +1 @@
-import{kt as e,n as t,t as n}from"./jsx-runtime-dm82oUI8.js";import{t as r}from"./CopyOutlined-DgLEpGTa.js";var i=n();function a({value:n,onCopy:a}){return(0,i.jsxs)(`div`,{className:`code-block`,children:[(0,i.jsx)(t.Paragraph,{code:!0,className:`code-block__value`,children:n}),a?(0,i.jsx)(e,{type:`text`,size:`small`,icon:(0,i.jsx)(r,{}),className:`code-block__copy`,onClick:()=>a(n),"aria-label":`Copy`}):null]})}export{a as t};
+import{kt as e,n as t,t as n}from"./jsx-runtime-dm82oUI8.js";import{t as r}from"./CopyOutlined-CjLq_nuP.js";var i=n();function a({value:n,onCopy:a}){return(0,i.jsxs)(`div`,{className:`code-block`,children:[(0,i.jsx)(t.Paragraph,{code:!0,className:`code-block__value`,children:n}),a?(0,i.jsx)(e,{type:`text`,size:`small`,icon:(0,i.jsx)(r,{}),className:`code-block__copy`,onClick:()=>a(n),"aria-label":`Copy`}):null]})}export{a as t};

+ 1 - 1
public/assets/CopyOutlined-DgLEpGTa.js → public/assets/CopyOutlined-CjLq_nuP.js

@@ -1 +1 @@
-import{a as e,n as t,r as n}from"./react-dom-stZKkA6x.js";import{u as r}from"./theme-Dy2IKOlV.js";var i=n((e=>{Object.defineProperty(e,"__esModule",{value:!0}),e.default={icon:{tag:`svg`,attrs:{viewBox:`64 64 896 896`,focusable:`false`},children:[{tag:`path`,attrs:{d:`M832 64H296c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h496v688c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8V96c0-17.7-14.3-32-32-32zM704 192H192c-17.7 0-32 14.3-32 32v530.7c0 8.5 3.4 16.6 9.4 22.6l173.3 173.3c2.2 2.2 4.7 4 7.4 5.5v1.9h4.2c3.5 1.3 7.2 2 11 2H704c17.7 0 32-14.3 32-32V224c0-17.7-14.3-32-32-32zM350 856.2L263.9 770H350v86.2zM664 888H414V746c0-22.1-17.9-40-40-40H232V264h432v624z`}}]},name:`copy`,theme:`outlined`}})),a=e(t()),o=e(i());function s(){return s=Object.assign?Object.assign.bind():function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e},s.apply(this,arguments)}var c=a.forwardRef((e,t)=>a.createElement(r,s({},e,{ref:t,icon:o.default})));export{c as t};
+import{a as e,n as t,r as n}from"./react-dom-stZKkA6x.js";import{u as r}from"./theme-DNlqAiGO.js";var i=n((e=>{Object.defineProperty(e,"__esModule",{value:!0}),e.default={icon:{tag:`svg`,attrs:{viewBox:`64 64 896 896`,focusable:`false`},children:[{tag:`path`,attrs:{d:`M832 64H296c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h496v688c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8V96c0-17.7-14.3-32-32-32zM704 192H192c-17.7 0-32 14.3-32 32v530.7c0 8.5 3.4 16.6 9.4 22.6l173.3 173.3c2.2 2.2 4.7 4 7.4 5.5v1.9h4.2c3.5 1.3 7.2 2 11 2H704c17.7 0 32-14.3 32-32V224c0-17.7-14.3-32-32-32zM350 856.2L263.9 770H350v86.2zM664 888H414V746c0-22.1-17.9-40-40-40H232V264h432v624z`}}]},name:`copy`,theme:`outlined`}})),a=e(t()),o=e(i());function s(){return s=Object.assign?Object.assign.bind():function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e},s.apply(this,arguments)}var c=a.forwardRef((e,t)=>a.createElement(r,s({},e,{ref:t,icon:o.default})));export{c as t};

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 1
public/assets/Dashboard-VPvfYTM4.js


Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 2 - 2
public/assets/DashboardCharts-s26_Bnz4.js


Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 0
public/assets/DnsApi-BFsXA3-6.js


Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 0
public/assets/DomainDetail-C_ScJIVm.js


Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 0
public/assets/Domains-ZaPKfS2a.js


+ 1 - 1
public/assets/EditOutlined-B4WrWHV3.js → public/assets/EditOutlined-BjvG9r8B.js

@@ -1 +1 @@
-import{a as e,n as t,r as n}from"./react-dom-stZKkA6x.js";import{u as r}from"./theme-Dy2IKOlV.js";var i=n((e=>{Object.defineProperty(e,"__esModule",{value:!0}),e.default={icon:{tag:`svg`,attrs:{viewBox:`64 64 896 896`,focusable:`false`},children:[{tag:`path`,attrs:{d:`M360 184h-8c4.4 0 8-3.6 8-8v8h304v-8c0 4.4 3.6 8 8 8h-8v72h72v-80c0-35.3-28.7-64-64-64H352c-35.3 0-64 28.7-64 64v80h72v-72zm504 72H160c-17.7 0-32 14.3-32 32v32c0 4.4 3.6 8 8 8h60.4l24.7 523c1.6 34.1 29.8 61 63.9 61h454c34.2 0 62.3-26.8 63.9-61l24.7-523H888c4.4 0 8-3.6 8-8v-32c0-17.7-14.3-32-32-32zM731.3 840H292.7l-24.2-512h487l-24.2 512z`}}]},name:`delete`,theme:`outlined`}})),a=e(t()),o=e(i());function s(){return s=Object.assign?Object.assign.bind():function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e},s.apply(this,arguments)}var c=a.forwardRef((e,t)=>a.createElement(r,s({},e,{ref:t,icon:o.default}))),l=e(n((e=>{Object.defineProperty(e,"__esModule",{value:!0}),e.default={icon:{tag:`svg`,attrs:{viewBox:`64 64 896 896`,focusable:`false`},children:[{tag:`path`,attrs:{d:`M257.7 752c2 0 4-.2 6-.5L431.9 722c2-.4 3.9-1.3 5.3-2.8l423.9-423.9a9.96 9.96 0 000-14.1L694.9 114.9c-1.9-1.9-4.4-2.9-7.1-2.9s-5.2 1-7.1 2.9L256.8 538.8c-1.5 1.5-2.4 3.3-2.8 5.3l-29.5 168.2a33.5 33.5 0 009.4 29.8c6.6 6.4 14.9 9.9 23.8 9.9zm67.4-174.4L687.8 215l73.3 73.3-362.7 362.6-88.9 15.7 15.6-89zM880 836H144c-17.7 0-32 14.3-32 32v36c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-36c0-17.7-14.3-32-32-32z`}}]},name:`edit`,theme:`outlined`}}))());function u(){return u=Object.assign?Object.assign.bind():function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e},u.apply(this,arguments)}var d=a.forwardRef((e,t)=>a.createElement(r,u({},e,{ref:t,icon:l.default})));export{c as n,d as t};
+import{a as e,n as t,r as n}from"./react-dom-stZKkA6x.js";import{u as r}from"./theme-DNlqAiGO.js";var i=n((e=>{Object.defineProperty(e,"__esModule",{value:!0}),e.default={icon:{tag:`svg`,attrs:{viewBox:`64 64 896 896`,focusable:`false`},children:[{tag:`path`,attrs:{d:`M360 184h-8c4.4 0 8-3.6 8-8v8h304v-8c0 4.4 3.6 8 8 8h-8v72h72v-80c0-35.3-28.7-64-64-64H352c-35.3 0-64 28.7-64 64v80h72v-72zm504 72H160c-17.7 0-32 14.3-32 32v32c0 4.4 3.6 8 8 8h60.4l24.7 523c1.6 34.1 29.8 61 63.9 61h454c34.2 0 62.3-26.8 63.9-61l24.7-523H888c4.4 0 8-3.6 8-8v-32c0-17.7-14.3-32-32-32zM731.3 840H292.7l-24.2-512h487l-24.2 512z`}}]},name:`delete`,theme:`outlined`}})),a=e(t()),o=e(i());function s(){return s=Object.assign?Object.assign.bind():function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e},s.apply(this,arguments)}var c=a.forwardRef((e,t)=>a.createElement(r,s({},e,{ref:t,icon:o.default}))),l=e(n((e=>{Object.defineProperty(e,"__esModule",{value:!0}),e.default={icon:{tag:`svg`,attrs:{viewBox:`64 64 896 896`,focusable:`false`},children:[{tag:`path`,attrs:{d:`M257.7 752c2 0 4-.2 6-.5L431.9 722c2-.4 3.9-1.3 5.3-2.8l423.9-423.9a9.96 9.96 0 000-14.1L694.9 114.9c-1.9-1.9-4.4-2.9-7.1-2.9s-5.2 1-7.1 2.9L256.8 538.8c-1.5 1.5-2.4 3.3-2.8 5.3l-29.5 168.2a33.5 33.5 0 009.4 29.8c6.6 6.4 14.9 9.9 23.8 9.9zm67.4-174.4L687.8 215l73.3 73.3-362.7 362.6-88.9 15.7 15.6-89zM880 836H144c-17.7 0-32 14.3-32 32v36c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-36c0-17.7-14.3-32-32-32z`}}]},name:`edit`,theme:`outlined`}}))());function u(){return u=Object.assign?Object.assign.bind():function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e},u.apply(this,arguments)}var d=a.forwardRef((e,t)=>a.createElement(r,u({},e,{ref:t,icon:l.default})));export{c as n,d as t};

+ 1 - 1
public/assets/EllipsisOutlined-oNNgezW9.js → public/assets/EllipsisOutlined-Bk3Ymyne.js

@@ -1 +1 @@
-import{a as e,n as t,r as n}from"./react-dom-stZKkA6x.js";import{u as r}from"./theme-Dy2IKOlV.js";var i=n((e=>{Object.defineProperty(e,"__esModule",{value:!0}),e.default={icon:{tag:`svg`,attrs:{viewBox:`64 64 896 896`,focusable:`false`},children:[{tag:`path`,attrs:{d:`M176 511a56 56 0 10112 0 56 56 0 10-112 0zm280 0a56 56 0 10112 0 56 56 0 10-112 0zm280 0a56 56 0 10112 0 56 56 0 10-112 0z`}}]},name:`ellipsis`,theme:`outlined`}})),a=e(t()),o=e(i());function s(){return s=Object.assign?Object.assign.bind():function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e},s.apply(this,arguments)}var c=a.forwardRef((e,t)=>a.createElement(r,s({},e,{ref:t,icon:o.default})));export{c as t};
+import{a as e,n as t,r as n}from"./react-dom-stZKkA6x.js";import{u as r}from"./theme-DNlqAiGO.js";var i=n((e=>{Object.defineProperty(e,"__esModule",{value:!0}),e.default={icon:{tag:`svg`,attrs:{viewBox:`64 64 896 896`,focusable:`false`},children:[{tag:`path`,attrs:{d:`M176 511a56 56 0 10112 0 56 56 0 10-112 0zm280 0a56 56 0 10112 0 56 56 0 10-112 0zm280 0a56 56 0 10112 0 56 56 0 10-112 0z`}}]},name:`ellipsis`,theme:`outlined`}})),a=e(t()),o=e(i());function s(){return s=Object.assign?Object.assign.bind():function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e},s.apply(this,arguments)}var c=a.forwardRef((e,t)=>a.createElement(r,s({},e,{ref:t,icon:o.default})));export{c as t};

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 0
public/assets/Inbox-BGre424Y.js


Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 0
public/assets/Inbox-xZSCNUoB.js


+ 1 - 1
public/assets/PlusOutlined-DNU7Mzq3.js → public/assets/PlusOutlined-CILz_LKx.js

@@ -1 +1 @@
-import{a as e,n as t,r as n}from"./react-dom-stZKkA6x.js";import{u as r}from"./theme-Dy2IKOlV.js";var i=n((e=>{Object.defineProperty(e,"__esModule",{value:!0}),e.default={icon:{tag:`svg`,attrs:{viewBox:`64 64 896 896`,focusable:`false`},children:[{tag:`path`,attrs:{d:`M482 152h60q8 0 8 8v704q0 8-8 8h-60q-8 0-8-8V160q0-8 8-8z`}},{tag:`path`,attrs:{d:`M192 474h672q8 0 8 8v60q0 8-8 8H160q-8 0-8-8v-60q0-8 8-8z`}}]},name:`plus`,theme:`outlined`}})),a=e(t()),o=e(i());function s(){return s=Object.assign?Object.assign.bind():function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e},s.apply(this,arguments)}var c=a.forwardRef((e,t)=>a.createElement(r,s({},e,{ref:t,icon:o.default})));export{c as t};
+import{a as e,n as t,r as n}from"./react-dom-stZKkA6x.js";import{u as r}from"./theme-DNlqAiGO.js";var i=n((e=>{Object.defineProperty(e,"__esModule",{value:!0}),e.default={icon:{tag:`svg`,attrs:{viewBox:`64 64 896 896`,focusable:`false`},children:[{tag:`path`,attrs:{d:`M482 152h60q8 0 8 8v704q0 8-8 8h-60q-8 0-8-8V160q0-8 8-8z`}},{tag:`path`,attrs:{d:`M192 474h672q8 0 8 8v60q0 8-8 8H160q-8 0-8-8v-60q0-8 8-8z`}}]},name:`plus`,theme:`outlined`}})),a=e(t()),o=e(i());function s(){return s=Object.assign?Object.assign.bind():function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e},s.apply(this,arguments)}var c=a.forwardRef((e,t)=>a.createElement(r,s({},e,{ref:t,icon:o.default})));export{c as t};

+ 1 - 1
public/assets/ReloadOutlined-C_t3CP1C.js → public/assets/ReloadOutlined-Cq8HKCaL.js

@@ -1 +1 @@
-import{a as e,n as t,r as n}from"./react-dom-stZKkA6x.js";import{u as r}from"./theme-Dy2IKOlV.js";var i=n((e=>{Object.defineProperty(e,"__esModule",{value:!0}),e.default={icon:{tag:`svg`,attrs:{viewBox:`64 64 896 896`,focusable:`false`},children:[{tag:`path`,attrs:{d:`M909.1 209.3l-56.4 44.1C775.8 155.1 656.2 92 521.9 92 290 92 102.3 279.5 102 511.5 101.7 743.7 289.8 932 521.9 932c181.3 0 335.8-115 394.6-276.1 1.5-4.2-.7-8.9-4.9-10.3l-56.7-19.5a8 8 0 00-10.1 4.8c-1.8 5-3.8 10-5.9 14.9-17.3 41-42.1 77.8-73.7 109.4A344.77 344.77 0 01655.9 829c-42.3 17.9-87.4 27-133.8 27-46.5 0-91.5-9.1-133.8-27A341.5 341.5 0 01279 755.2a342.16 342.16 0 01-73.7-109.4c-17.9-42.4-27-87.4-27-133.9s9.1-91.5 27-133.9c17.3-41 42.1-77.8 73.7-109.4 31.6-31.6 68.4-56.4 109.3-73.8 42.3-17.9 87.4-27 133.8-27 46.5 0 91.5 9.1 133.8 27a341.5 341.5 0 01109.3 73.8c9.9 9.9 19.2 20.4 27.8 31.4l-60.2 47a8 8 0 003 14.1l175.6 43c5 1.2 9.9-2.6 9.9-7.7l.8-180.9c-.1-6.6-7.8-10.3-13-6.2z`}}]},name:`reload`,theme:`outlined`}})),a=e(t()),o=e(i());function s(){return s=Object.assign?Object.assign.bind():function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e},s.apply(this,arguments)}var c=a.forwardRef((e,t)=>a.createElement(r,s({},e,{ref:t,icon:o.default})));export{c as t};
+import{a as e,n as t,r as n}from"./react-dom-stZKkA6x.js";import{u as r}from"./theme-DNlqAiGO.js";var i=n((e=>{Object.defineProperty(e,"__esModule",{value:!0}),e.default={icon:{tag:`svg`,attrs:{viewBox:`64 64 896 896`,focusable:`false`},children:[{tag:`path`,attrs:{d:`M909.1 209.3l-56.4 44.1C775.8 155.1 656.2 92 521.9 92 290 92 102.3 279.5 102 511.5 101.7 743.7 289.8 932 521.9 932c181.3 0 335.8-115 394.6-276.1 1.5-4.2-.7-8.9-4.9-10.3l-56.7-19.5a8 8 0 00-10.1 4.8c-1.8 5-3.8 10-5.9 14.9-17.3 41-42.1 77.8-73.7 109.4A344.77 344.77 0 01655.9 829c-42.3 17.9-87.4 27-133.8 27-46.5 0-91.5-9.1-133.8-27A341.5 341.5 0 01279 755.2a342.16 342.16 0 01-73.7-109.4c-17.9-42.4-27-87.4-27-133.9s9.1-91.5 27-133.9c17.3-41 42.1-77.8 73.7-109.4 31.6-31.6 68.4-56.4 109.3-73.8 42.3-17.9 87.4-27 133.8-27 46.5 0 91.5 9.1 133.8 27a341.5 341.5 0 01109.3 73.8c9.9 9.9 19.2 20.4 27.8 31.4l-60.2 47a8 8 0 003 14.1l175.6 43c5 1.2 9.9-2.6 9.9-7.7l.8-180.9c-.1-6.6-7.8-10.3-13-6.2z`}}]},name:`reload`,theme:`outlined`}})),a=e(t()),o=e(i());function s(){return s=Object.assign?Object.assign.bind():function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e},s.apply(this,arguments)}var c=a.forwardRef((e,t)=>a.createElement(r,s({},e,{ref:t,icon:o.default})));export{c as t};

+ 1 - 1
public/assets/SearchOutlined-BDeH_Qai.js → public/assets/SearchOutlined-Bw_KR-cI.js

@@ -1 +1 @@
-import{a as e,n as t,r as n}from"./react-dom-stZKkA6x.js";import{u as r}from"./theme-Dy2IKOlV.js";var i=n((e=>{Object.defineProperty(e,"__esModule",{value:!0}),e.default={icon:{tag:`svg`,attrs:{viewBox:`64 64 896 896`,focusable:`false`},children:[{tag:`path`,attrs:{d:`M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0011.6 0l43.6-43.5a8.2 8.2 0 000-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z`}}]},name:`search`,theme:`outlined`}})),a=e(t()),o=e(i());function s(){return s=Object.assign?Object.assign.bind():function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e},s.apply(this,arguments)}var c=a.forwardRef((e,t)=>a.createElement(r,s({},e,{ref:t,icon:o.default})));export{c as t};
+import{a as e,n as t,r as n}from"./react-dom-stZKkA6x.js";import{u as r}from"./theme-DNlqAiGO.js";var i=n((e=>{Object.defineProperty(e,"__esModule",{value:!0}),e.default={icon:{tag:`svg`,attrs:{viewBox:`64 64 896 896`,focusable:`false`},children:[{tag:`path`,attrs:{d:`M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0011.6 0l43.6-43.5a8.2 8.2 0 000-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z`}}]},name:`search`,theme:`outlined`}})),a=e(t()),o=e(i());function s(){return s=Object.assign?Object.assign.bind():function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e},s.apply(this,arguments)}var c=a.forwardRef((e,t)=>a.createElement(r,s({},e,{ref:t,icon:o.default})));export{c as t};

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 0
public/assets/SendingLogs-BkFSn1G1.js


Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 0
public/assets/Settings-DSNkOz-2.js


Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 0
public/assets/SmtpCredentials-O4rUapPO.js


Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 0
public/assets/StatusPill-Bcw_ne6L.js


+ 1 - 1
public/assets/StatusTag-C9tkl7IN.js → public/assets/StatusTag-CBp0RCE4.js

@@ -1 +1 @@
-import{a as e}from"./theme-Dy2IKOlV.js";import{t}from"./jsx-runtime-dm82oUI8.js";import{t as n}from"./badge-CT2dDAGA.js";import{t as r}from"./StatusPill-CAqweeP1.js";import{r as i}from"./domain-model-BLOrIAIx.js";var a=t();function o(e){switch(e){case`success`:return`success`;case`warning`:case`processing`:return`warning`;case`error`:return`error`;default:return`neutral`}}function s({status:t,record:s,label:c,mode:l=`tag`}){let{t:u}=e(),d=i(s||{status:t}),f=c||u(`status.${d.key}`);return l===`badge`?(0,a.jsx)(n,{status:d.color==="default"?`default`:d.color,text:f}):(0,a.jsx)(r,{tone:o(d.color),children:f})}export{s as t};
+import{a as e}from"./theme-DNlqAiGO.js";import{t}from"./jsx-runtime-dm82oUI8.js";import{t as n}from"./badge-CT2dDAGA.js";import{t as r}from"./StatusPill-Bcw_ne6L.js";import{r as i}from"./domain-model-BLOrIAIx.js";var a=t();function o(e){switch(e){case`success`:return`success`;case`warning`:case`processing`:return`warning`;case`error`:return`error`;default:return`neutral`}}function s({status:t,record:s,label:c,mode:l=`tag`}){let{t:u}=e(),d=i(s||{status:t}),f=c||u(`status.${d.key}`);return l===`badge`?(0,a.jsx)(n,{status:d.color==="default"?`default`:d.color,text:f}):(0,a.jsx)(r,{tone:o(d.color),children:f})}export{s as t};

+ 1 - 1
public/assets/ThunderboltOutlined-zxxPM5e8.js → public/assets/ThunderboltOutlined-CMOzejTs.js

@@ -1 +1 @@
-import{a as e,n as t,r as n}from"./react-dom-stZKkA6x.js";import{u as r}from"./theme-Dy2IKOlV.js";var i=n((e=>{Object.defineProperty(e,"__esModule",{value:!0}),e.default={icon:{tag:`svg`,attrs:{viewBox:`64 64 896 896`,focusable:`false`},children:[{tag:`path`,attrs:{d:`M848 359.3H627.7L825.8 109c4.1-5.3.4-13-6.3-13H436c-2.8 0-5.5 1.5-6.9 4L170 547.5c-3.1 5.3.7 12 6.9 12h174.4l-89.4 357.6c-1.9 7.8 7.5 13.3 13.3 7.7L853.5 373c5.2-4.9 1.7-13.7-5.5-13.7zM378.2 732.5l60.3-241H281.1l189.6-327.4h224.6L487 427.4h211L378.2 732.5z`}}]},name:`thunderbolt`,theme:`outlined`}})),a=e(t()),o=e(i());function s(){return s=Object.assign?Object.assign.bind():function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e},s.apply(this,arguments)}var c=a.forwardRef((e,t)=>a.createElement(r,s({},e,{ref:t,icon:o.default})));export{c as t};
+import{a as e,n as t,r as n}from"./react-dom-stZKkA6x.js";import{u as r}from"./theme-DNlqAiGO.js";var i=n((e=>{Object.defineProperty(e,"__esModule",{value:!0}),e.default={icon:{tag:`svg`,attrs:{viewBox:`64 64 896 896`,focusable:`false`},children:[{tag:`path`,attrs:{d:`M848 359.3H627.7L825.8 109c4.1-5.3.4-13-6.3-13H436c-2.8 0-5.5 1.5-6.9 4L170 547.5c-3.1 5.3.7 12 6.9 12h174.4l-89.4 357.6c-1.9 7.8 7.5 13.3 13.3 7.7L853.5 373c5.2-4.9 1.7-13.7-5.5-13.7zM378.2 732.5l60.3-241H281.1l189.6-327.4h224.6L487 427.4h211L378.2 732.5z`}}]},name:`thunderbolt`,theme:`outlined`}})),a=e(t()),o=e(i());function s(){return s=Object.assign?Object.assign.bind():function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e},s.apply(this,arguments)}var c=a.forwardRef((e,t)=>a.createElement(r,s({},e,{ref:t,icon:o.default})));export{c as t};

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 0
public/assets/Webhooks-C4qtVEsF.js


Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 1 - 1
public/assets/form-BRrMNJiX.js


Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 1 - 1
public/assets/grid-DcIFgt5N.js


Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 1
public/assets/index-CBPGvhVC.js


Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 0
public/assets/list-B3SBzx24.js


Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 0
public/assets/login-r6bTsl_n.js


+ 1 - 1
public/assets/modal-hO5zE2jK.js → public/assets/modal-BNBPl4Qp.js

@@ -1 +1 @@
-import{a as e,n as t}from"./react-dom-stZKkA6x.js";import{C as n,D as r,S as i,T as a,_ as o,b as s,f as c,g as l,h as u,m as d,p as f,v as p,w as m,x as h,y as g}from"./theme-Dy2IKOlV.js";import{Mr as _,Rn as v,cn as y,rt as b}from"./jsx-runtime-dm82oUI8.js";var x=e(t()),S=e(_()),C=function(e,t){var n={};for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&t.indexOf(r)<0&&(n[r]=e[r]);if(e!=null&&typeof Object.getOwnPropertySymbols==`function`)for(var i=0,r=Object.getOwnPropertySymbols(e);i<r.length;i++)t.indexOf(r[i])<0&&Object.prototype.propertyIsEnumerable.call(e,r[i])&&(n[r[i]]=e[r[i]]);return n},w=b(e=>{let{prefixCls:t,className:i,closeIcon:o,closable:s,type:c,title:l,children:u,footer:d}=e,f=C(e,[`prefixCls`,`className`,`closeIcon`,`closable`,`type`,`title`,`children`,`footer`]),{getPrefixCls:p}=x.useContext(v),g=p(),_=t||p(`modal`),b=y(g),[w,T,E]=n(_,b),D=`${_}-confirm`,O={};return O=c?{closable:s??!1,title:``,footer:``,children:x.createElement(h,Object.assign({},e,{prefixCls:_,confirmPrefixCls:D,rootPrefixCls:g,content:u}))}:{closable:s??!0,title:l,footer:d!==null&&x.createElement(m,Object.assign({},e)),children:u},w(x.createElement(r,Object.assign({prefixCls:_,className:(0,S.default)(T,`${_}-pure-panel`,c&&D,c&&`${D}-${c}`,i,E,b)},f,{closeIcon:a(_,o),closable:s},O)))});function T(e){return f(g(e))}var E=i;E.useModal=c,E.info=function(e){return f(o(e))},E.success=function(e){return f(p(e))},E.error=function(e){return f(l(e))},E.warning=T,E.warn=T,E.confirm=function(e){return f(u(e))},E.destroyAll=function(){for(;s.length;){let e=s.pop();e&&e()}},E.config=d,E._InternalPanelDoNotUseOrYouWillBeFired=w;export{E as t};
+import{a as e,n as t}from"./react-dom-stZKkA6x.js";import{C as n,D as r,S as i,T as a,_ as o,b as s,f as c,g as l,h as u,m as d,p as f,v as p,w as m,x as h,y as g}from"./theme-DNlqAiGO.js";import{Mr as _,Rn as v,cn as y,rt as b}from"./jsx-runtime-dm82oUI8.js";var x=e(t()),S=e(_()),C=function(e,t){var n={};for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&t.indexOf(r)<0&&(n[r]=e[r]);if(e!=null&&typeof Object.getOwnPropertySymbols==`function`)for(var i=0,r=Object.getOwnPropertySymbols(e);i<r.length;i++)t.indexOf(r[i])<0&&Object.prototype.propertyIsEnumerable.call(e,r[i])&&(n[r[i]]=e[r[i]]);return n},w=b(e=>{let{prefixCls:t,className:i,closeIcon:o,closable:s,type:c,title:l,children:u,footer:d}=e,f=C(e,[`prefixCls`,`className`,`closeIcon`,`closable`,`type`,`title`,`children`,`footer`]),{getPrefixCls:p}=x.useContext(v),g=p(),_=t||p(`modal`),b=y(g),[w,T,E]=n(_,b),D=`${_}-confirm`,O={};return O=c?{closable:s??!1,title:``,footer:``,children:x.createElement(h,Object.assign({},e,{prefixCls:_,confirmPrefixCls:D,rootPrefixCls:g,content:u}))}:{closable:s??!0,title:l,footer:d!==null&&x.createElement(m,Object.assign({},e)),children:u},w(x.createElement(r,Object.assign({prefixCls:_,className:(0,S.default)(T,`${_}-pure-panel`,c&&D,c&&`${D}-${c}`,i,E,b)},f,{closeIcon:a(_,o),closable:s},O)))});function T(e){return f(g(e))}var E=i;E.useModal=c,E.info=function(e){return f(o(e))},E.success=function(e){return f(p(e))},E.error=function(e){return f(l(e))},E.warning=T,E.warn=T,E.confirm=function(e){return f(u(e))},E.destroyAll=function(){for(;s.length;){let e=s.pop();e&&e()}},E.config=d,E._InternalPanelDoNotUseOrYouWillBeFired=w;export{E as t};

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 0
public/assets/popconfirm-DHzKhZl9.js


+ 1 - 0
public/assets/row-Cmd9RpeX.js

@@ -0,0 +1 @@
+import{n as e,r as t}from"./grid-DcIFgt5N.js";var n=t,r=e;export{n,r as t};

+ 0 - 1
public/assets/row-cwQH2RQY.js

@@ -1 +0,0 @@
-import{n as e,r as t}from"./grid-CvLJ37H1.js";var n=t,r=e;export{n,r as t};

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 0
public/assets/table-CHt5qwx3.js


Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 0
public/assets/theme-DNlqAiGO.js


Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 0
public/assets/theme-e1juCvr1.css


+ 3 - 3
public/index.html

@@ -4,12 +4,12 @@
     <meta charset="UTF-8" />
     <meta name="viewport" content="width=device-width, initial-scale=1.0" />
     <title>MailHub</title>
-    <script type="module" crossorigin src="/assets/index-CrChtXhS.js"></script>
+    <script type="module" crossorigin src="/assets/index-CBPGvhVC.js"></script>
     <link rel="modulepreload" crossorigin href="/assets/react-dom-stZKkA6x.js">
     <link rel="modulepreload" crossorigin href="/assets/modulepreload-polyfill-Dezn_h7o.js">
     <link rel="modulepreload" crossorigin href="/assets/jsx-runtime-dm82oUI8.js">
-    <link rel="modulepreload" crossorigin href="/assets/theme-Dy2IKOlV.js">
-    <link rel="stylesheet" crossorigin href="/assets/theme-C5uSFXHJ.css">
+    <link rel="modulepreload" crossorigin href="/assets/theme-DNlqAiGO.js">
+    <link rel="stylesheet" crossorigin href="/assets/theme-e1juCvr1.css">
     <link rel="stylesheet" crossorigin href="/assets/index-Tu04tXLf.css">
   </head>
   <body>

+ 5 - 5
public/login.html

@@ -4,17 +4,17 @@
     <meta charset="UTF-8" />
     <meta name="viewport" content="width=device-width, initial-scale=1.0" />
     <title>MailHub Auth</title>
-    <script type="module" crossorigin src="/assets/login-BlHoEHHV.js"></script>
+    <script type="module" crossorigin src="/assets/login-r6bTsl_n.js"></script>
     <link rel="modulepreload" crossorigin href="/assets/react-dom-stZKkA6x.js">
     <link rel="modulepreload" crossorigin href="/assets/modulepreload-polyfill-Dezn_h7o.js">
     <link rel="modulepreload" crossorigin href="/assets/jsx-runtime-dm82oUI8.js">
-    <link rel="modulepreload" crossorigin href="/assets/theme-Dy2IKOlV.js">
-    <link rel="modulepreload" crossorigin href="/assets/grid-CvLJ37H1.js">
+    <link rel="modulepreload" crossorigin href="/assets/theme-DNlqAiGO.js">
+    <link rel="modulepreload" crossorigin href="/assets/grid-DcIFgt5N.js">
     <link rel="modulepreload" crossorigin href="/assets/segmented-DUpU-ING.js">
     <link rel="modulepreload" crossorigin href="/assets/useForm-sdDvuRHy.js">
-    <link rel="modulepreload" crossorigin href="/assets/form-Dc_x_ybP.js">
+    <link rel="modulepreload" crossorigin href="/assets/form-BRrMNJiX.js">
     <link rel="modulepreload" crossorigin href="/assets/input-0_Xo22Fl.js">
-    <link rel="stylesheet" crossorigin href="/assets/theme-C5uSFXHJ.css">
+    <link rel="stylesheet" crossorigin href="/assets/theme-e1juCvr1.css">
   </head>
   <body>
     <div id="auth-root"></div>

+ 4 - 2
src/frontend/i18n/index.js

@@ -320,7 +320,8 @@ const messages = {
     'inbox.copyRaw': '复制原文',
     'inbox.recipients': '收件人',
     'inbox.textBody': '文本正文',
-    'inbox.htmlBody': 'HTML 源码',
+    'inbox.htmlBody': 'HTML 预览',
+    'inbox.htmlSource': 'HTML 源码',
     'inbox.rawMessage': '原始 MIME',
     'inbox.noTextBody': '暂无文本正文',
     'inbox.noHtmlBody': '暂无 HTML 正文',
@@ -954,7 +955,8 @@ const messages = {
     'inbox.copyRaw': 'Copy raw',
     'inbox.recipients': 'Recipients',
     'inbox.textBody': 'Text body',
-    'inbox.htmlBody': 'HTML source',
+    'inbox.htmlBody': 'HTML preview',
+    'inbox.htmlSource': 'HTML source',
     'inbox.rawMessage': 'Raw MIME',
     'inbox.noTextBody': 'No text body',
     'inbox.noHtmlBody': 'No HTML body',

+ 10 - 0
src/frontend/styles.css

@@ -763,6 +763,16 @@ code {
   word-break: break-word;
 }
 
+.inbox-html-preview {
+  background: #ffffff;
+  border: 1px solid var(--mh-border);
+  border-radius: 8px;
+  display: block;
+  height: min(640px, 62vh);
+  min-height: 360px;
+  width: 100%;
+}
+
 .delivery-log-entry {
   display: grid;
   gap: 8px;

+ 32 - 4
src/pages/Inbox.tsx

@@ -69,7 +69,7 @@ interface MailboxFormValues {
 }
 
 const standardFolders = ['INBOX', 'Sent', 'Drafts', 'Trash', 'Junk', 'Archive'];
-type MessageTab = 'text' | 'html' | 'raw';
+type MessageTab = 'text' | 'html' | 'source' | 'raw';
 type MailFolder = InboundFolder & { countsAvailable: boolean };
 type MailboxSort = 'activity' | 'address' | 'unread' | 'messages';
 type RouteSort = 'domain' | 'configured';
@@ -775,6 +775,7 @@ function MessageDetail({ message, loading, error, mutationError, activeTab, onTa
   if (loading) return <Skeleton active paragraph={{ rows: 12 }} />;
   if (error) return <Alert type="error" showIcon message={error} />;
   if (!message) return <EmptyState description={t('inbox.messageDetail')} />;
+  const resolvedTab = activeTab === 'text' && !message.textBody && message.htmlBody ? 'html' : activeTab;
   return (
     <Space direction="vertical" size={20} className="full-width">
       {mutationError ? <Alert type="warning" showIcon message={mutationError} /> : null}
@@ -784,9 +785,10 @@ function MessageDetail({ message, loading, error, mutationError, activeTab, onTa
         <Descriptions.Item label={t('inbox.recipients')}>{message.recipients.join(', ') || '—'}</Descriptions.Item>
         <Descriptions.Item label={t('logs.messageId')}><Typography.Text code copyable={{ onCopy: () => onCopy(message.messageId) }}>{message.messageId || '—'}</Typography.Text></Descriptions.Item>
       </Descriptions>
-      <Tabs activeKey={activeTab} onChange={(key) => onTabChange(normalizeMessageTab(key))} items={[
+      <Tabs activeKey={resolvedTab} onChange={(key) => onTabChange(normalizeMessageTab(key))} items={[
         { key: 'text', label: t('inbox.textBody'), children: message.textBody ? <pre className="inbox-message-body">{message.textBody}</pre> : <EmptyState description={t('inbox.noTextBody')} /> },
-        { key: 'html', label: t('inbox.htmlBody'), children: message.htmlBody ? <CodeBlock value={message.htmlBody} onCopy={onCopy} /> : <EmptyState description={t('inbox.noHtmlBody')} /> },
+        { key: 'html', label: t('inbox.htmlBody'), children: message.htmlBody ? <HtmlMessagePreview html={message.htmlBody} title={t('inbox.htmlBody')} /> : <EmptyState description={t('inbox.noHtmlBody')} /> },
+        { key: 'source', label: t('inbox.htmlSource'), children: message.htmlBody ? <CodeBlock value={message.htmlBody} onCopy={onCopy} /> : <EmptyState description={t('inbox.noHtmlBody')} /> },
         { key: 'raw', label: t('inbox.rawMessage'), children: message.rawMessage ? <CodeBlock value={message.rawMessage} onCopy={onCopy} /> : <EmptyState description={t('inbox.noRawMessage')} /> }
       ]} />
     </Space>
@@ -794,7 +796,33 @@ function MessageDetail({ message, loading, error, mutationError, activeTab, onTa
 }
 
 function normalizeMessageTab(value: string | null): MessageTab {
-  return value === 'html' || value === 'raw' ? value : 'text';
+  return value === 'html' || value === 'source' || value === 'raw' ? value : 'text';
+}
+
+function HtmlMessagePreview({ html, title }: { html: string; title: string }) {
+  return (
+    <iframe
+      title={title}
+      className="inbox-html-preview"
+      sandbox=""
+      referrerPolicy="no-referrer"
+      srcDoc={htmlPreviewDocument(html)}
+    />
+  );
+}
+
+function htmlPreviewDocument(html: string) {
+  const previewHead = [
+    '<meta charset="utf-8">',
+    '<meta name="viewport" content="width=device-width, initial-scale=1">',
+    '<meta http-equiv="Content-Security-Policy" content="default-src \'none\'; img-src data: blob: http: https:; style-src \'unsafe-inline\' http: https:; font-src data: http: https:; script-src \'none\'; connect-src \'none\'; frame-src \'none\'; object-src \'none\'; base-uri \'none\'; form-action \'none\'">',
+    '<base target="_blank">',
+    '<style>html,body{margin:0;padding:12px;background:#fff;color:#111827;font:14px/1.5 -apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;overflow-wrap:anywhere;}img{max-width:100%;height:auto;}table{max-width:100%;}a{color:#4F46E5;}</style>'
+  ].join('');
+  const source = String(html || '');
+  if (/<head(?:\s[^>]*)?>/i.test(source)) return source.replace(/<head(?:\s[^>]*)?>/i, (match) => `${match}${previewHead}`);
+  if (/<html(?:\s[^>]*)?>/i.test(source)) return source.replace(/<html(?:\s[^>]*)?>/i, (match) => `${match}<head>${previewHead}</head>`);
+  return `<!doctype html><html><head>${previewHead}</head><body>${source}</body></html>`;
 }
 
 function inboxListPath(searchParams: URLSearchParams, message: MailMessage | null) {

+ 25 - 1
test/ui/inbox-navigation.test.tsx

@@ -60,7 +60,7 @@ describe('Inbox detail return path', () => {
     });
     await waitFor(() => expect(messages).toHaveBeenCalledWith(expect.objectContaining({ mailboxId: 2, folder: 'Archive' })));
 
-    await user.click(screen.getByRole('tab', { name: 'HTML 源码' }));
+    await user.click(screen.getByRole('tab', { name: 'HTML 预览' }));
     await waitFor(() => expect(new URLSearchParams(router.state.location.search).get('tab')).toBe('html'));
     expect(detailHistoryState(router.state.location.state)).toEqual({
       listPath: '/inbox?mailboxId=2&folder=Archive',
@@ -104,6 +104,30 @@ describe('Inbox detail return path', () => {
 
     expect(await screen.findByRole('button', { name: 'Close' })).toBeTruthy();
   });
+
+  it('renders HTML-only messages in a sandboxed preview while keeping the source tab available', async () => {
+    const user = userEvent.setup();
+    const htmlOnly = {
+      ...messageFixture(31, 1, 'INBOX', 'HTML only'),
+      textBody: '',
+      htmlBody: '<html><body><h1>Rendered invoice</h1><script>window.parent.pwned = true</script></body></html>'
+    };
+    mockInboxApis([mailboxFixture(1)], [htmlOnly]);
+    vi.spyOn(api, 'inboundMessage').mockResolvedValue({ message: htmlOnly });
+    const router = createInboxRouter(['/inbox/messages/31?mailboxId=1&folder=INBOX'], 0);
+    renderRouter(router);
+
+    const preview = await screen.findByTitle('HTML 预览') as HTMLIFrameElement;
+    expect(screen.getByRole('tab', { name: 'HTML 预览' }).getAttribute('aria-selected')).toBe('true');
+    expect(preview.getAttribute('sandbox')).toBe('');
+    expect(preview.getAttribute('referrerpolicy')).toBe('no-referrer');
+    expect(preview.getAttribute('srcdoc')).toContain('<h1>Rendered invoice</h1>');
+    expect(preview.getAttribute('srcdoc')).toContain("script-src 'none'");
+
+    await user.click(screen.getByRole('tab', { name: 'HTML 源码' }));
+    expect(screen.getByText(/Rendered invoice/)).toBeTruthy();
+    await waitFor(() => expect(new URLSearchParams(router.state.location.search).get('tab')).toBe('source'));
+  });
 });
 
 describe('Inbox request and mailbox behavior', () => {

+ 1 - 1
test/ui/operations-navigation.test.tsx

@@ -37,7 +37,7 @@ describe('Operational navigation state', () => {
 
     await user.click(await screen.findByRole('button', { name: 'Quarterly report · sender@example.test' }));
     await waitFor(() => expect(router.state.location.pathname).toBe('/inbox/messages/9'));
-    await user.click(await screen.findByRole('tab', { name: 'HTML 源码' }));
+    await user.click(await screen.findByRole('tab', { name: 'HTML 预览' }));
     await waitFor(() => expect(new URLSearchParams(router.state.location.search).get('tab')).toBe('html'));
     await act(async () => {
       await router.navigate(-1);

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно