mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-07-08 11:56:59 +00:00
Preserve quoted email history during AI edits
This commit is contained in:
+3
-3
@@ -219,8 +219,8 @@
|
||||
}, { once: true });
|
||||
})();
|
||||
</script>
|
||||
<link rel="stylesheet" href="/static/style.css?v=20260630emailcomposer">
|
||||
<link rel="modulepreload" href="/static/app.js?v=20260630emailcomposer">
|
||||
<link rel="stylesheet" href="/static/style.css?v=20260630emailquote">
|
||||
<link rel="modulepreload" href="/static/app.js?v=20260630emailquote">
|
||||
<link rel="modulepreload" href="/static/js/chat.js">
|
||||
<link rel="modulepreload" href="/static/js/ui.js">
|
||||
<link rel="modulepreload" href="/static/js/sessions.js">
|
||||
@@ -2512,7 +2512,7 @@
|
||||
<script type="module" src="/static/js/settings.js"></script>
|
||||
<script type="module" src="/static/js/admin.js"></script>
|
||||
<script type="module" src="/static/js/assistant.js"></script>
|
||||
<script type="module" src="/static/app.js?v=20260630emailcomposer"></script> <!-- app.js must be LAST -->
|
||||
<script type="module" src="/static/app.js?v=20260630emailquote"></script> <!-- app.js must be LAST -->
|
||||
<script type="module" src="/static/js/init.js"></script>
|
||||
<script type="module" src="/static/js/a11y.js"></script>
|
||||
<script nonce="{{CSP_NONCE}}">if('serviceWorker' in navigator){navigator.serviceWorker.register('/static/sw.js').catch(()=>{});}</script>
|
||||
|
||||
+66
-15
@@ -2540,17 +2540,43 @@ import { bindMenuDismiss, dismissOrRemove } from './escMenuStack.js';
|
||||
_emailStreamAnimFrame = requestAnimationFrame(tick);
|
||||
}
|
||||
|
||||
function _stripEmailReplyQuoteText(text) {
|
||||
function _emailQuoteStartIndex(lines) {
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = String(lines[i] || '').trim();
|
||||
if (
|
||||
/^[-_=–—\s]{3,}(previous|original|forwarded)\s+(message|email|mail)[-_=–—\s]{3,}$/i.test(line)
|
||||
|| /^On .+ wrote:\s*$/i.test(line)
|
||||
|| /^-{2,}\s*Original Message\s*-{2,}$/i.test(line)
|
||||
) {
|
||||
return i;
|
||||
}
|
||||
// Some pasted/converted threads lose the separator and start directly
|
||||
// with mail headers. Treat that as quoted history only when the nearby
|
||||
// lines look like a real header block.
|
||||
if (/^From:\s+\S/i.test(line)) {
|
||||
const nearby = lines.slice(i + 1, i + 8).map(l => String(l || '').trim());
|
||||
if (nearby.some(l => /^To:\s+/i.test(l)) || nearby.some(l => /^Subject:\s+/i.test(l))) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
function _splitEmailReplyQuote(text) {
|
||||
const original = String(text || '');
|
||||
if (!original) return { body: '', stripped: false };
|
||||
if (!original) return { body: '', quote: '', stripped: false };
|
||||
const lines = original.split('\n');
|
||||
const quoteIdx = lines.findIndex(line =>
|
||||
/^-{5,}\s*Previous message\s*-{5,}$/i.test(line.trim())
|
||||
|| /^On .+ wrote:\s*$/i.test(line.trim())
|
||||
);
|
||||
if (quoteIdx <= 0) return { body: original.trim(), stripped: false };
|
||||
const quoteIdx = _emailQuoteStartIndex(lines);
|
||||
if (quoteIdx < 0) return { body: original.trim(), quote: '', stripped: false };
|
||||
const body = lines.slice(0, quoteIdx).join('\n').trim();
|
||||
return { body, stripped: !!body };
|
||||
const quote = lines.slice(quoteIdx).join('\n').trim();
|
||||
return { body, quote, stripped: true };
|
||||
}
|
||||
|
||||
function _stripEmailReplyQuoteText(text) {
|
||||
const split = _splitEmailReplyQuote(text);
|
||||
return { body: split.body, stripped: split.stripped };
|
||||
}
|
||||
|
||||
function _emailReplyOwnText(text) {
|
||||
@@ -6543,12 +6569,8 @@ import { bindMenuDismiss, dismissOrRemove } from './escMenuStack.js';
|
||||
const doc = docs.get(docId);
|
||||
if (!doc) return;
|
||||
const fields = _parseEmailHeader(doc.content || '');
|
||||
const lines = String(fields.body || '').split('\n');
|
||||
const quoteIdx = lines.findIndex(line =>
|
||||
/^-{5,}\s*Previous message\s*-{5,}$/i.test(line.trim())
|
||||
|| /^On .+ wrote:\s*$/i.test(line.trim())
|
||||
);
|
||||
const quote = quoteIdx >= 0 ? lines.slice(quoteIdx).join('\n') : '';
|
||||
const oldSplit = _splitEmailReplyQuote(fields.body || '');
|
||||
const quote = oldSplit.quote;
|
||||
const ownText = _emailReplyOwnText(fields.body || '');
|
||||
if (!force && ownText && !/^(\[AI reply draft will appear here\]|Drafting AI reply)/i.test(ownText)) {
|
||||
if (uiModule) uiModule.showToast('AI reply ready, but draft was edited');
|
||||
@@ -9940,7 +9962,7 @@ import { bindMenuDismiss, dismissOrRemove } from './escMenuStack.js';
|
||||
// and enterDiffMode().
|
||||
if (_diffModeActive) exitDiffMode(true);
|
||||
let docId = data.doc_id;
|
||||
const newContent = data.content || '';
|
||||
let newContent = data.content || '';
|
||||
|
||||
// Migrate streaming temp doc to real ID
|
||||
if (streamingId && streamingId.startsWith('_streaming_') && docs.has(streamingId)) {
|
||||
@@ -9990,6 +10012,35 @@ import { bindMenuDismiss, dismissOrRemove } from './escMenuStack.js';
|
||||
const textarea = document.getElementById('doc-editor-textarea');
|
||||
const oldContent = (docId === activeDocId && textarea) ? textarea.value : '';
|
||||
const isExistingDoc = docs.has(docId);
|
||||
if (isExistingDoc) {
|
||||
const existingDoc = docs.get(docId);
|
||||
const existingLang = ((existingDoc?.language || data.language || '') + '').toLowerCase();
|
||||
const oldFields = _parseEmailHeader(existingDoc?.content || '');
|
||||
const newFields = _parseEmailHeader(newContent || '');
|
||||
if (
|
||||
existingLang === 'email'
|
||||
&& oldFields.body
|
||||
&& newFields.body
|
||||
&& (oldFields.inReplyTo || oldFields.sourceUid || newFields.inReplyTo || newFields.sourceUid)
|
||||
) {
|
||||
const oldSplit = _splitEmailReplyQuote(oldFields.body);
|
||||
if (oldSplit.quote) {
|
||||
const newSplit = _splitEmailReplyQuote(newFields.body);
|
||||
const nextBody = `${(newSplit.body || newFields.body || '').trim()}\n\n${oldSplit.quote}`.trim();
|
||||
newContent = _buildEmailContent(
|
||||
newFields.to || oldFields.to,
|
||||
newFields.subject || oldFields.subject,
|
||||
newFields.inReplyTo || oldFields.inReplyTo,
|
||||
newFields.references || oldFields.references,
|
||||
nextBody,
|
||||
newFields.sourceUid || oldFields.sourceUid,
|
||||
newFields.sourceFolder || oldFields.sourceFolder,
|
||||
newFields.cc || oldFields.cc,
|
||||
newFields.bcc || oldFields.bcc,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add or update in docs map
|
||||
if (isExistingDoc) {
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@
|
||||
// - Other static assets (images/fonts/libs): cache-first with bg refresh.
|
||||
// - API / non-GET: never cached.
|
||||
// Bump CACHE_NAME whenever the precache list or SW logic changes.
|
||||
const CACHE_NAME = 'odysseus-v330';
|
||||
const CACHE_NAME = 'odysseus-v331';
|
||||
|
||||
// Core shell precached on install so repeat opens are instant without any
|
||||
// network wait. Keep this list in sync with the <script type="module"> tags
|
||||
|
||||
Reference in New Issue
Block a user