mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-07-15 12:58:04 +00:00
Preserve quoted email history during AI edits
This commit is contained in:
+3
-3
@@ -219,8 +219,8 @@
|
|||||||
}, { once: true });
|
}, { once: true });
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
<link rel="stylesheet" href="/static/style.css?v=20260630emailcomposer">
|
<link rel="stylesheet" href="/static/style.css?v=20260630emailquote">
|
||||||
<link rel="modulepreload" href="/static/app.js?v=20260630emailcomposer">
|
<link rel="modulepreload" href="/static/app.js?v=20260630emailquote">
|
||||||
<link rel="modulepreload" href="/static/js/chat.js">
|
<link rel="modulepreload" href="/static/js/chat.js">
|
||||||
<link rel="modulepreload" href="/static/js/ui.js">
|
<link rel="modulepreload" href="/static/js/ui.js">
|
||||||
<link rel="modulepreload" href="/static/js/sessions.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/settings.js"></script>
|
||||||
<script type="module" src="/static/js/admin.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/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/init.js"></script>
|
||||||
<script type="module" src="/static/js/a11y.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>
|
<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);
|
_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 || '');
|
const original = String(text || '');
|
||||||
if (!original) return { body: '', stripped: false };
|
if (!original) return { body: '', quote: '', stripped: false };
|
||||||
const lines = original.split('\n');
|
const lines = original.split('\n');
|
||||||
const quoteIdx = lines.findIndex(line =>
|
const quoteIdx = _emailQuoteStartIndex(lines);
|
||||||
/^-{5,}\s*Previous message\s*-{5,}$/i.test(line.trim())
|
if (quoteIdx < 0) return { body: original.trim(), quote: '', stripped: false };
|
||||||
|| /^On .+ wrote:\s*$/i.test(line.trim())
|
|
||||||
);
|
|
||||||
if (quoteIdx <= 0) return { body: original.trim(), stripped: false };
|
|
||||||
const body = lines.slice(0, quoteIdx).join('\n').trim();
|
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) {
|
function _emailReplyOwnText(text) {
|
||||||
@@ -6543,12 +6569,8 @@ import { bindMenuDismiss, dismissOrRemove } from './escMenuStack.js';
|
|||||||
const doc = docs.get(docId);
|
const doc = docs.get(docId);
|
||||||
if (!doc) return;
|
if (!doc) return;
|
||||||
const fields = _parseEmailHeader(doc.content || '');
|
const fields = _parseEmailHeader(doc.content || '');
|
||||||
const lines = String(fields.body || '').split('\n');
|
const oldSplit = _splitEmailReplyQuote(fields.body || '');
|
||||||
const quoteIdx = lines.findIndex(line =>
|
const quote = oldSplit.quote;
|
||||||
/^-{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 ownText = _emailReplyOwnText(fields.body || '');
|
const ownText = _emailReplyOwnText(fields.body || '');
|
||||||
if (!force && ownText && !/^(\[AI reply draft will appear here\]|Drafting AI reply)/i.test(ownText)) {
|
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');
|
if (uiModule) uiModule.showToast('AI reply ready, but draft was edited');
|
||||||
@@ -9940,7 +9962,7 @@ import { bindMenuDismiss, dismissOrRemove } from './escMenuStack.js';
|
|||||||
// and enterDiffMode().
|
// and enterDiffMode().
|
||||||
if (_diffModeActive) exitDiffMode(true);
|
if (_diffModeActive) exitDiffMode(true);
|
||||||
let docId = data.doc_id;
|
let docId = data.doc_id;
|
||||||
const newContent = data.content || '';
|
let newContent = data.content || '';
|
||||||
|
|
||||||
// Migrate streaming temp doc to real ID
|
// Migrate streaming temp doc to real ID
|
||||||
if (streamingId && streamingId.startsWith('_streaming_') && docs.has(streamingId)) {
|
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 textarea = document.getElementById('doc-editor-textarea');
|
||||||
const oldContent = (docId === activeDocId && textarea) ? textarea.value : '';
|
const oldContent = (docId === activeDocId && textarea) ? textarea.value : '';
|
||||||
const isExistingDoc = docs.has(docId);
|
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
|
// Add or update in docs map
|
||||||
if (isExistingDoc) {
|
if (isExistingDoc) {
|
||||||
|
|||||||
+1
-1
@@ -7,7 +7,7 @@
|
|||||||
// - Other static assets (images/fonts/libs): cache-first with bg refresh.
|
// - Other static assets (images/fonts/libs): cache-first with bg refresh.
|
||||||
// - API / non-GET: never cached.
|
// - API / non-GET: never cached.
|
||||||
// Bump CACHE_NAME whenever the precache list or SW logic changes.
|
// 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
|
// 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
|
// network wait. Keep this list in sync with the <script type="module"> tags
|
||||||
|
|||||||
Reference in New Issue
Block a user