mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-10-31 10:12:21 +01:00
fix(email): retain inline image selections before input reset
This commit is contained in:
+1
-1
@@ -12,7 +12,7 @@ import presetsModule from './js/presets.js?v=20260908personaname1';
|
|||||||
import searchModule from './js/search.js';
|
import searchModule from './js/search.js';
|
||||||
import chatModule from './js/chat.js?v=20260917toolttft1';
|
import chatModule from './js/chat.js?v=20260917toolttft1';
|
||||||
import compareModule from './js/compare/index.js?v=20260909mobilepaneaddscroll1';
|
import compareModule from './js/compare/index.js?v=20260909mobilepaneaddscroll1';
|
||||||
import documentModule from './js/document.js?v=20261028emailpickerfiles1';
|
import documentModule from './js/document.js?v=20261028emailpickerfiles2';
|
||||||
import searchChatModule from './js/search-chat.js';
|
import searchChatModule from './js/search-chat.js';
|
||||||
import { makeWindowDraggable } from './js/windowDrag.js';
|
import { makeWindowDraggable } from './js/windowDrag.js';
|
||||||
import {
|
import {
|
||||||
|
|||||||
+1
-1
@@ -2682,7 +2682,7 @@
|
|||||||
<script type="module" src="/static/js/search.js"></script>
|
<script type="module" src="/static/js/search.js"></script>
|
||||||
<script type="module" src="/static/js/spinner.js"></script>
|
<script type="module" src="/static/js/spinner.js"></script>
|
||||||
<script type="module" src="/static/js/tts-ai.js"></script>
|
<script type="module" src="/static/js/tts-ai.js"></script>
|
||||||
<script type="module" src="/static/js/document.js?v=20261028emailpickerfiles1"></script>
|
<script type="module" src="/static/js/document.js?v=20261028emailpickerfiles2"></script>
|
||||||
<script type="module" src="/static/js/gallery.js?v=20260910promptcopy1"></script>
|
<script type="module" src="/static/js/gallery.js?v=20260910promptcopy1"></script>
|
||||||
<script type="module" src="/static/js/chatRenderer.js?v=20260914metricssummary1"></script>
|
<script type="module" src="/static/js/chatRenderer.js?v=20260914metricssummary1"></script>
|
||||||
<script type="module" src="/static/js/codeRunner.js?v=20260831richtexttools91"></script>
|
<script type="module" src="/static/js/codeRunner.js?v=20260831richtexttools91"></script>
|
||||||
|
|||||||
+1
-1
@@ -16,7 +16,7 @@ import spinnerModule from './spinner.js';
|
|||||||
import presetsModule from './presets.js?v=20260908personaname1';
|
import presetsModule from './presets.js?v=20260908personaname1';
|
||||||
import fileHandlerModule from './fileHandler.js?v=20260909mobileattachmentedit1';
|
import fileHandlerModule from './fileHandler.js?v=20260909mobileattachmentedit1';
|
||||||
import searchModule from './search.js';
|
import searchModule from './search.js';
|
||||||
import documentModule from './document.js?v=20261028emailpickerfiles1';
|
import documentModule from './document.js?v=20261028emailpickerfiles2';
|
||||||
import * as emailInbox from './emailInbox.js?v=20260914aireply4';
|
import * as emailInbox from './emailInbox.js?v=20260914aireply4';
|
||||||
import codeRunnerModule from './codeRunner.js?v=20260831richtexttools91';
|
import codeRunnerModule from './codeRunner.js?v=20260831richtexttools91';
|
||||||
import slashCommands, { initSlashCommands, isCommand, handleSlashCommand, handleSetupInput, handleSetupWizard, typewriterInto } from './slashCommands.js?v=20260921chatgptusage1';
|
import slashCommands, { initSlashCommands, isCommand, handleSlashCommand, handleSetupInput, handleSetupWizard, typewriterInto } from './slashCommands.js?v=20260921chatgptusage1';
|
||||||
|
|||||||
@@ -1889,7 +1889,7 @@ function _activateEntityAnchor(e, forcedAnchor = null) {
|
|||||||
} catch {}
|
} catch {}
|
||||||
});
|
});
|
||||||
} else if (kind === 'document') {
|
} else if (kind === 'document') {
|
||||||
import('./document.js?v=20261028emailpickerfiles1').then(mod => {
|
import('./document.js?v=20261028emailpickerfiles2').then(mod => {
|
||||||
const open = mod.loadDocument
|
const open = mod.loadDocument
|
||||||
|| mod.openDocument
|
|| mod.openDocument
|
||||||
|| (mod.default && (mod.default.loadDocument || mod.default.openDocument));
|
|| (mod.default && (mod.default.loadDocument || mod.default.openDocument));
|
||||||
@@ -3656,7 +3656,7 @@ export function addMessage(role, content, modelName, metadata) {
|
|||||||
|
|
||||||
b.querySelectorAll('[data-doc-edit-ref]').forEach(button => {
|
b.querySelectorAll('[data-doc-edit-ref]').forEach(button => {
|
||||||
button.addEventListener('click', () => {
|
button.addEventListener('click', () => {
|
||||||
import('./document.js?v=20261028emailpickerfiles1').then(mod => {
|
import('./document.js?v=20261028emailpickerfiles2').then(mod => {
|
||||||
const restore = mod.restoreSelectionReference
|
const restore = mod.restoreSelectionReference
|
||||||
|| mod.default?.restoreSelectionReference;
|
|| mod.default?.restoreSelectionReference;
|
||||||
return restore?.(button.dataset.docEditRef || '', {
|
return restore?.(button.dataset.docEditRef || '', {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import Storage from './storage.js';
|
|||||||
import themeModule from './theme.js?v=20260911organsrain1';
|
import themeModule from './theme.js?v=20260911organsrain1';
|
||||||
import markdownModule from './markdown.js';
|
import markdownModule from './markdown.js';
|
||||||
import sessionModule from './sessions.js';
|
import sessionModule from './sessions.js';
|
||||||
import documentModule from './document.js?v=20261028emailpickerfiles1';
|
import documentModule from './document.js?v=20261028emailpickerfiles2';
|
||||||
|
|
||||||
// Tool approvals are control-plane submits for the current chat. chat.js
|
// Tool approvals are control-plane submits for the current chat. chat.js
|
||||||
// deliberately leaves the composer untouched, then programmatically clicks the
|
// deliberately leaves the composer untouched, then programmatically clicks the
|
||||||
|
|||||||
@@ -5928,7 +5928,7 @@ import { attachColorPicker } from './colorPicker.js?v=20260910eyedropper1';
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function _handleMarkdownImageUpload(e) {
|
async function _handleMarkdownImageUpload(e) {
|
||||||
const files = e.target.files;
|
const files = Array.from(e.target.files || []);
|
||||||
e.target.value = '';
|
e.target.value = '';
|
||||||
await _uploadMarkdownImages(files);
|
await _uploadMarkdownImages(files);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -187,7 +187,7 @@ function _wireAttachmentHandlers(reader, folder) {
|
|||||||
ownerModal.classList.add('hidden');
|
ownerModal.classList.add('hidden');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const docMod = await import('../document.js?v=20261028emailpickerfiles1');
|
const docMod = await import('../document.js?v=20261028emailpickerfiles2');
|
||||||
const load = (docMod && docMod.loadDocument) || (docMod && docMod.default && docMod.default.loadDocument);
|
const load = (docMod && docMod.loadDocument) || (docMod && docMod.default && docMod.default.loadDocument);
|
||||||
if (typeof load === 'function') {
|
if (typeof load === 'function') {
|
||||||
await load(json.doc_id);
|
await load(json.doc_id);
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import modelsModule from './models.js';
|
|||||||
import chatRenderer from './chatRenderer.js?v=20260914metricssummary1';
|
import chatRenderer from './chatRenderer.js?v=20260914metricssummary1';
|
||||||
import spinnerModule from './spinner.js';
|
import spinnerModule from './spinner.js';
|
||||||
import themeModule from './theme.js?v=20260911organsrain1';
|
import themeModule from './theme.js?v=20260911organsrain1';
|
||||||
import documentModule from './document.js?v=20261028emailpickerfiles1';
|
import documentModule from './document.js?v=20261028emailpickerfiles2';
|
||||||
import workspaceModule from './workspace.js';
|
import workspaceModule from './workspace.js';
|
||||||
import settingsModule from './settings.js?v=20260912writingstyle3';
|
import settingsModule from './settings.js?v=20260912writingstyle3';
|
||||||
import cookbookModule from './cookbook.js';
|
import cookbookModule from './cookbook.js';
|
||||||
|
|||||||
+2
-2
@@ -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-v660-email-picker-files';
|
const CACHE_NAME = 'odysseus-v661-email-picker-files';
|
||||||
|
|
||||||
// KaTeX resolves these from its own stylesheet, so caching the CSS without them
|
// KaTeX resolves these from its own stylesheet, so caching the CSS without them
|
||||||
// gives offline math fallback glyphs instead of proper typesetting.
|
// gives offline math fallback glyphs instead of proper typesetting.
|
||||||
@@ -80,7 +80,7 @@ const PRECACHE = [
|
|||||||
'/static/js/search.js',
|
'/static/js/search.js',
|
||||||
'/static/js/spinner.js',
|
'/static/js/spinner.js',
|
||||||
'/static/js/tts-ai.js',
|
'/static/js/tts-ai.js',
|
||||||
'/static/js/document.js?v=20261028emailpickerfiles1',
|
'/static/js/document.js?v=20261028emailpickerfiles2',
|
||||||
'/static/js/gallery.js?v=20260910promptcopy1',
|
'/static/js/gallery.js?v=20260910promptcopy1',
|
||||||
'/static/js/chatRenderer.js?v=20260914metricssummary1',
|
'/static/js/chatRenderer.js?v=20260914metricssummary1',
|
||||||
'/static/js/codeRunner.js?v=20260831richtexttools91',
|
'/static/js/codeRunner.js?v=20260831richtexttools91',
|
||||||
|
|||||||
@@ -9,35 +9,41 @@ import pytest
|
|||||||
from tests.helpers.document_source import function_body
|
from tests.helpers.document_source import function_body
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("selections", [[["one.txt"]], [["one.txt", "two.csv"]], [["one.txt"], ["one.txt"]], [[]]])
|
@pytest.mark.parametrize("handler,uploader", [("_handleAttachUpload", "_uploadComposeFiles"), ("_handleMarkdownImageUpload", "_uploadMarkdownImages")])
|
||||||
def test_compose_file_selection_survives_reset(tmp_path, selections):
|
@pytest.mark.parametrize("selections", [[["one.png"]], [["one.png", "two.png"]], [["one.png"], ["one.png"]], [[]]])
|
||||||
|
def test_compose_file_selection_survives_reset(tmp_path, selections, handler, uploader):
|
||||||
if not shutil.which("node"):
|
if not shutil.which("node"):
|
||||||
pytest.skip("node not installed")
|
pytest.skip("node not installed")
|
||||||
for name, content in [("one.txt", "Synthetic attachment one\n"), ("two.csv", 'name,value\n"fixture",2\n')]:
|
repo = Path(__file__).resolve().parents[1]
|
||||||
(tmp_path / name).write_text(content, encoding="utf-8")
|
for name, icon in [("one.png", "icon-192.png"), ("two.png", "icon-512.png")]:
|
||||||
|
shutil.copyfile(repo / "static/icons" / icon, tmp_path / name)
|
||||||
script = r"""
|
script = r"""
|
||||||
const fs=require('node:fs'),assert=require('node:assert/strict');
|
const fs=require('node:fs'),assert=require('node:assert/strict'),crypto=require('node:crypto');
|
||||||
let chromium;try{({chromium}=require('playwright'));}catch(e){if(e.code==='MODULE_NOT_FOUND')process.exit(77);throw e;}
|
let chromium;try{({chromium}=require('playwright'));}catch(e){if(e.code==='MODULE_NOT_FOUND')process.exit(77);throw e;}
|
||||||
(async()=>{
|
(async()=>{
|
||||||
const browser=await chromium.launch({headless:true});
|
const browser=await chromium.launch({headless:true});
|
||||||
try{
|
try{
|
||||||
const page=await browser.newPage();const errors=[];page.on('pageerror',e=>errors.push(e.message));
|
const page=await browser.newPage();const errors=[];page.on('pageerror',e=>errors.push(e.message));
|
||||||
|
await page.goto(process.env.ODYSSEUS_TEST_STATIC_ORIGIN+'/static/js/storage.js');
|
||||||
await page.setContent('<input type="file" multiple aria-label="Compose attachments">');
|
await page.setContent('<input type="file" multiple aria-label="Compose attachments">');
|
||||||
await page.addScriptTag({content:`
|
await page.addScriptTag({content:`
|
||||||
window.uploads=[];window.finished=0;
|
window.uploads=[];window.finished=0;
|
||||||
async function _uploadComposeFiles(files){
|
async function __UPLOADER__(files){
|
||||||
for(const file of files)window.uploads.push({name:file.name,text:await file.text()});
|
for(const file of files){
|
||||||
|
const hash=Array.from(new Uint8Array(await crypto.subtle.digest('SHA-256',await file.arrayBuffer()))).map(x=>x.toString(16).padStart(2,'0')).join('');
|
||||||
|
window.uploads.push({name:file.name,hash});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
__HANDLER__
|
__HANDLER__
|
||||||
document.querySelector('input').addEventListener('change',async event=>{
|
document.querySelector('input').addEventListener('change',async event=>{
|
||||||
await _handleAttachUpload(event);window.finished++;
|
await __HANDLER_NAME__(event);window.finished++;
|
||||||
});
|
});
|
||||||
`});
|
`});
|
||||||
const selections=JSON.parse(process.argv[1]),root=process.argv[2],expected=[];
|
const selections=JSON.parse(process.argv[1]),root=process.argv[2],expected=[];
|
||||||
for(const [index,names] of selections.entries()){
|
for(const [index,names] of selections.entries()){
|
||||||
await page.getByLabel('Compose attachments').setInputFiles(names.map(name=>root+'/'+name));
|
await page.getByLabel('Compose attachments').setInputFiles(names.map(name=>root+'/'+name));
|
||||||
await page.waitForFunction(n=>window.finished===n,index+1);
|
await page.waitForFunction(n=>window.finished===n,index+1);
|
||||||
expected.push(...names.map(name=>({name,text:fs.readFileSync(root+'/'+name,'utf8')})));
|
expected.push(...names.map(name=>({name,hash:crypto.createHash('sha256').update(fs.readFileSync(root+'/'+name)).digest('hex')})));
|
||||||
assert.deepEqual(await page.evaluate(()=>window.uploads),expected);
|
assert.deepEqual(await page.evaluate(()=>window.uploads),expected);
|
||||||
assert.equal(await page.getByLabel('Compose attachments').inputValue(),'');
|
assert.equal(await page.getByLabel('Compose attachments').inputValue(),'');
|
||||||
assert.equal(await page.getByLabel('Compose attachments').evaluate(el=>el.files.length),0);
|
assert.equal(await page.getByLabel('Compose attachments').evaluate(el=>el.files.length),0);
|
||||||
@@ -45,7 +51,7 @@ let chromium;try{({chromium}=require('playwright'));}catch(e){if(e.code==='MODUL
|
|||||||
assert.deepEqual(errors,[]);console.log(JSON.stringify({files:expected.length}));
|
assert.deepEqual(errors,[]);console.log(JSON.stringify({files:expected.length}));
|
||||||
}finally{await browser.close();}
|
}finally{await browser.close();}
|
||||||
})().catch(e=>{console.error(e);process.exitCode=1;});
|
})().catch(e=>{console.error(e);process.exitCode=1;});
|
||||||
""".replace("__HANDLER__", function_body("_handleAttachUpload"))
|
""".replace("__HANDLER__", function_body(handler)).replace("__HANDLER_NAME__", handler).replace("__UPLOADER__", uploader)
|
||||||
result = subprocess.run(["node", "-e", script, json.dumps(selections), str(tmp_path)],
|
result = subprocess.run(["node", "-e", script, json.dumps(selections), str(tmp_path)],
|
||||||
cwd=Path(__file__).resolve().parents[1], capture_output=True,
|
cwd=Path(__file__).resolve().parents[1], capture_output=True,
|
||||||
text=True, timeout=30)
|
text=True, timeout=30)
|
||||||
|
|||||||
Reference in New Issue
Block a user