fix: 文件选择器改为页内iframe弹窗 + 乱码编码修复 + API路径修复
- pickPath()从window.open改为页内iframe模态弹窗(遮罩+关闭按钮) - files.html添加meta charset+CSS link,独立模式自动加载files.js - API路径统一通过_apiUrl()构建,适配主面板和iframe两种上下文 - 独立iframe模式注入body/container覆写样式(无topbar) - 选择模式栏移至底部margin-top:auto紧凑排列 - MD3风格toggle开关(显示隐藏复选框) - CSS版本号缓存破坏 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -39,6 +39,12 @@ window.FilesModule = {
|
||||
},
|
||||
|
||||
/* ── API helpers ── */
|
||||
_apiBase: '',
|
||||
_apiUrl: function(path) {
|
||||
// Use explicit base if set (standalone/iframe mode), otherwise relative
|
||||
if (this._apiBase) return this._apiBase + '/api/files' + path;
|
||||
return './api/files' + path;
|
||||
},
|
||||
_api: function(url, opts) {
|
||||
opts = opts || {};
|
||||
opts.credentials = 'include';
|
||||
@@ -47,16 +53,16 @@ window.FilesModule = {
|
||||
return r.json();
|
||||
});
|
||||
},
|
||||
_get: function(url) { return this._api(url); },
|
||||
_get: function(url) { return this._api(this._apiUrl(url)); },
|
||||
_post: function(url, body) {
|
||||
return this._api(url, {method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify(body)});
|
||||
return this._api(this._apiUrl(url), {method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify(body)});
|
||||
},
|
||||
|
||||
/* ── Jump to path (with existence check) ── */
|
||||
_jumpTo: function(path) {
|
||||
var self = this;
|
||||
// Probe: try to list the target path
|
||||
this._get('./api/files/info?path=' + encodeURIComponent(path))
|
||||
this._get('/info?path=' + encodeURIComponent(path))
|
||||
.then(function(info) {
|
||||
if (info.error) { self._pathError(info.error); return; }
|
||||
if (info.is_dir) {
|
||||
@@ -87,7 +93,7 @@ window.FilesModule = {
|
||||
refresh: function() {
|
||||
var self = this;
|
||||
var showHidden = document.getElementById('fm-hidden')?.checked ? '1' : '0';
|
||||
self._get('./api/files/list?path=' + encodeURIComponent(self.currentPath) + '&show_hidden=' + showHidden)
|
||||
self._get('/list?path=' + encodeURIComponent(self.currentPath) + '&show_hidden=' + showHidden)
|
||||
.then(function(d) {
|
||||
self._render(d);
|
||||
}).catch(function(e) {
|
||||
@@ -187,37 +193,48 @@ window.FilesModule = {
|
||||
document.getElementById('fm-context').style.display = 'none';
|
||||
};
|
||||
|
||||
// Picker banner
|
||||
// Picker banner — at bottom
|
||||
if (self.pickerMode) {
|
||||
var banner = document.getElementById('fm-picker-banner');
|
||||
if (!banner) {
|
||||
banner = document.createElement('div');
|
||||
banner.id = 'fm-picker-banner';
|
||||
banner.innerHTML = '<div style="display:flex;align-items:center;gap:8px;padding:8px 16px;background:var(--primary-container);color:var(--md-sys-color-on-primary-container);border-radius:var(--shape-xs);margin-bottom:8px;font-size:.85rem">' +
|
||||
banner.innerHTML = '<div style="display:flex;align-items:center;gap:8px;padding:6px 12px;background:var(--primary-container);color:var(--md-sys-color-on-primary-container);border-radius:var(--shape-xs);font-size:.82rem">' +
|
||||
'<span>📂 选择' + (self.pickerMode === 'file' ? '文件' : '目录') + '模式</span>' +
|
||||
'<button class="btn btn-sm btn-filled" onclick="FilesModule._pickResult(FilesModule.currentPath)" style="margin-left:auto">选择当前目录</button>' +
|
||||
'<button class="btn btn-sm btn-outlined" onclick="FilesModule._cancelPicker()">取消</button>' +
|
||||
'</div>';
|
||||
list.parentNode.insertBefore(banner, list);
|
||||
list.parentNode.appendChild(banner);
|
||||
list.style.flex = '';
|
||||
banner.style.marginTop = 'auto';
|
||||
banner.style.paddingBottom = '0';
|
||||
} else {
|
||||
banner.style.display = 'block';
|
||||
}
|
||||
} else {
|
||||
var oldBanner = document.getElementById('fm-picker-banner');
|
||||
if (oldBanner) oldBanner.style.display = 'none';
|
||||
}
|
||||
},
|
||||
|
||||
/* ── Picker ── */
|
||||
_pickResult: function(path) {
|
||||
if (window.opener) {
|
||||
window.opener.postMessage(JSON.stringify({action:'fm-picked', path: path, callback_id: this.pickerCallback}), '*');
|
||||
window.close();
|
||||
} else if (window.parent !== window) {
|
||||
// In iframe (picker mode): post to parent, parent closes overlay
|
||||
if (window.parent !== window) {
|
||||
window.parent.postMessage(JSON.stringify({action:'fm-picked', path: path, callback_id: this.pickerCallback}), '*');
|
||||
} else {
|
||||
navigator.clipboard?.writeText(path);
|
||||
alert('已选择: ' + path + '\n(路径已复制到剪贴板)');
|
||||
this.pickerMode = false;
|
||||
this.refresh();
|
||||
return;
|
||||
}
|
||||
// Standalone clipboard fallback
|
||||
navigator.clipboard?.writeText(path);
|
||||
alert('已选择: ' + path);
|
||||
this.pickerMode = false;
|
||||
this.refresh();
|
||||
},
|
||||
_cancelPicker: function() {
|
||||
if (window.parent !== window) {
|
||||
window.parent.postMessage(JSON.stringify({action:'fm-picked', path: null, callback_id: this.pickerCallback}), '*');
|
||||
return;
|
||||
}
|
||||
this.pickerMode = false;
|
||||
this.refresh();
|
||||
},
|
||||
@@ -227,7 +244,7 @@ window.FilesModule = {
|
||||
var self = this;
|
||||
var name = prompt('新建文件名:');
|
||||
if (!name) return;
|
||||
this._post('./api/files/touch', {path: this.currentPath, name: name})
|
||||
this._post('/touch', {path: this.currentPath, name: name})
|
||||
.then(function() { self.refresh(); })
|
||||
.catch(function(e) { alert('创建失败: ' + e.message); });
|
||||
},
|
||||
@@ -235,7 +252,7 @@ window.FilesModule = {
|
||||
var self = this;
|
||||
var name = prompt('新建文件夹名:');
|
||||
if (!name) return;
|
||||
this._post('./api/files/mkdir', {path: this.currentPath, name: name})
|
||||
this._post('/mkdir', {path: this.currentPath, name: name})
|
||||
.then(function() { self.refresh(); })
|
||||
.catch(function(e) { alert('创建失败: ' + e.message); });
|
||||
},
|
||||
@@ -244,7 +261,7 @@ window.FilesModule = {
|
||||
if (!files || !files.length) return;
|
||||
var form = new FormData();
|
||||
for (var i = 0; i < files.length; i++) form.append('file', files[i]);
|
||||
fetch('./api/files/upload?path=' + encodeURIComponent(self.currentPath), {method:'POST', credentials:'include', body:form})
|
||||
fetch(self._apiUrl('/upload?path=') + encodeURIComponent(self.currentPath), {method:'POST', credentials:'include', body:form})
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(d) {
|
||||
if (d.ok) self.refresh();
|
||||
@@ -255,7 +272,7 @@ window.FilesModule = {
|
||||
/* ── Editor ── */
|
||||
openEditor: function(path, name) {
|
||||
var self = this;
|
||||
this._get('./api/files/read?path=' + encodeURIComponent(path))
|
||||
this._get('/read?path=' + encodeURIComponent(path))
|
||||
.then(function(d) {
|
||||
document.getElementById('fm-editor-title').textContent = '📝 ' + (name || d.name);
|
||||
document.getElementById('fm-editor-textarea').value = d.content;
|
||||
@@ -269,7 +286,7 @@ window.FilesModule = {
|
||||
saveFile: function() {
|
||||
var self = this;
|
||||
var ta = document.getElementById('fm-editor-textarea');
|
||||
this._post('./api/files/write', {path: ta.dataset.path, content: ta.value})
|
||||
this._post('/write', {path: ta.dataset.path, content: ta.value})
|
||||
.then(function() { self.closeEditor(); self.refresh(); })
|
||||
.catch(function(e) { alert('保存失败: ' + e.message); });
|
||||
},
|
||||
@@ -286,7 +303,7 @@ window.FilesModule = {
|
||||
},
|
||||
ctxDownload: function() {
|
||||
var t = this.contextTarget;
|
||||
if (t) window.open('./api/files/download?path=' + encodeURIComponent(t.path), '_blank');
|
||||
if (t) window.open(FilesModule._apiUrl('/download?path=') + encodeURIComponent(t.path), '_blank');
|
||||
document.getElementById('fm-context').style.display = 'none';
|
||||
},
|
||||
ctxRename: function() {
|
||||
@@ -295,7 +312,7 @@ window.FilesModule = {
|
||||
if (!t) return;
|
||||
var nn = prompt('新名称:', t.name);
|
||||
if (!nn || nn === t.name) return;
|
||||
this._post('./api/files/rename', {path: t.path, new_name: nn})
|
||||
this._post('/rename', {path: t.path, new_name: nn})
|
||||
.then(function() { self.refresh(); })
|
||||
.catch(function(e) { alert('重命名失败: ' + e.message); });
|
||||
document.getElementById('fm-context').style.display = 'none';
|
||||
@@ -316,7 +333,7 @@ window.FilesModule = {
|
||||
confirmDelete: function() {
|
||||
var self = this;
|
||||
if (!this.toDelete) return;
|
||||
this._post('./api/files/delete', {path: this.toDelete.path})
|
||||
this._post('/delete', {path: this.toDelete.path})
|
||||
.then(function() { self.toDelete = null; self.closeDialog(); self.refresh(); })
|
||||
.catch(function(e) { alert('删除失败: ' + e.message); });
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user