Polish official plugin workbench UI

This commit is contained in:
2026-07-02 00:49:29 +08:00
parent 0e079e8290
commit d6b8ab17f1
9 changed files with 458 additions and 219 deletions
+63 -21
View File
@@ -28,23 +28,30 @@
}
var STYLES = [
'.search-root{height:100%;min-height:0;display:flex;flex-direction:column;background:#0d0d1a;color:#e0e0e0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",system-ui,sans-serif}',
'.search-toolbar{display:flex;align-items:center;gap:.5rem;padding:.55rem .75rem;border-bottom:1px solid #16213e;background:#12122a;flex-shrink:0;flex-wrap:wrap}',
'.search-input{flex:1;min-width:180px;font-size:.86rem;padding:.42rem .55rem;border:1px solid #333;border-radius:4px;background:#0d0d1a;color:#e0e0e0;outline:none}',
'.search-input:focus{border-color:#4ecca3}',
'.search-btn{font-size:.8rem;padding:.42rem .7rem;border:1px solid #333;border-radius:4px;background:#1a1a2e;color:#ddd;cursor:pointer}',
'.search-btn:hover{border-color:#4ecca3;background:#2a2a4e}',
'.search-root{height:100%;min-height:0;display:flex;flex-direction:column;background:var(--vt-color-background,#101020);color:var(--vt-color-text-primary,#f4f7fb);font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",system-ui,sans-serif}',
'.search-toolbar{display:flex;align-items:center;gap:.5rem;min-height:2.75rem;padding:.55rem .75rem;border-bottom:1px solid var(--vt-color-border,#202b46);background:var(--vt-color-surface-muted,#111629);flex-shrink:0;flex-wrap:wrap}',
'.search-input{flex:1;min-width:180px;font-size:.86rem;padding:.42rem .55rem;border:1px solid var(--vt-color-border-strong,#2c456a);border-radius:var(--vt-radius-sm,4px);background:#0f1424;color:var(--vt-color-text-primary,#f4f7fb);outline:none}',
'.search-input:focus{border-color:var(--vt-color-accent,#4ecca3);box-shadow:var(--vt-focus-ring,0 0 0 2px rgba(78,204,163,.34))}',
'.search-btn{font-size:.8rem;min-height:2rem;padding:.42rem .7rem;border:1px solid var(--vt-color-border-strong,#2c456a);border-radius:var(--vt-radius-md,6px);background:var(--vt-color-surface-hover,#1b2440);color:var(--vt-color-text-secondary,#b7c0d4);cursor:pointer}',
'.search-btn:hover{border-color:var(--vt-color-accent,#4ecca3);background:var(--vt-color-surface-hover,#1b2440);color:var(--vt-color-text-primary,#f4f7fb)}',
'.search-btn:disabled{opacity:.45;cursor:default}',
'.search-scope{font-size:.72rem;color:#8b8ba8;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:18rem}',
'.search-status{font-size:.78rem;color:#8b8ba8;padding:.45rem .75rem;border-bottom:1px solid rgba(22,33,62,.55);flex-shrink:0}',
'.search-status.error{color:#e74c3c}',
'.search-scope{font-size:.72rem;color:var(--vt-color-text-muted,#7f8aa3);min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:18rem}',
'.search-status{font-size:.78rem;color:var(--vt-color-text-muted,#7f8aa3);padding:.45rem .75rem;border-bottom:1px solid rgba(32,43,70,.72);flex-shrink:0}',
'.search-status.error{color:var(--vt-color-danger,#e94560)}',
'.search-alert{margin:.65rem .75rem 0;border:1px solid rgba(233,69,96,.45);border-radius:var(--vt-radius-md,6px);background:var(--vt-color-danger-muted,rgba(233,69,96,.14));color:#ffc6ce;padding:.55rem .65rem;font-size:.78rem}',
'.search-alert[hidden]{display:none}',
'.search-alert summary{cursor:pointer;color:#fff}',
'.search-results{flex:1;min-height:0;overflow:auto}',
'.search-empty{height:100%;display:flex;align-items:center;justify-content:center;color:#666;font-size:.9rem;padding:2rem;text-align:center}',
'.search-result{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:.5rem;padding:.7rem .85rem;border-bottom:1px solid rgba(22,33,62,.55)}',
'.search-result:hover{background:#17172d}',
'.search-path{font-size:.84rem;color:#4ecca3;word-break:break-word}',
'.search-snippet{margin-top:.25rem;font-size:.8rem;line-height:1.45;color:#cfcfe0;white-space:pre-wrap;overflow-wrap:anywhere}',
'.search-meta{margin-top:.28rem;font-size:.72rem;color:#777}',
'.search-empty{height:100%;display:flex;align-items:center;justify-content:center;color:var(--vt-color-text-muted,#7f8aa3);font-size:.9rem;padding:2rem;text-align:center}',
'.search-result{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:.75rem;align-items:center;margin:.5rem .75rem 0;padding:.7rem .8rem;border:1px solid var(--vt-color-border,#202b46);border-radius:var(--vt-radius-lg,8px);background:var(--vt-color-surface,#15152c)}',
'.search-result:hover{background:var(--vt-color-surface-hover,#1b2440)}',
'.search-result-head{display:flex;align-items:center;gap:.5rem;min-width:0}',
'.search-type{display:inline-flex;align-items:center;min-height:1.25rem;padding:.1rem .4rem;border:1px solid rgba(78,204,163,.4);border-radius:var(--vt-radius-sm,4px);background:var(--vt-color-accent-muted,rgba(78,204,163,.14));color:var(--vt-color-accent,#4ecca3);font-size:.68rem;font-weight:650;text-transform:uppercase;letter-spacing:.03em}',
'.search-title{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:.86rem;color:var(--vt-color-text-primary,#f4f7fb);font-weight:650}',
'.search-path{margin-top:.2rem;font-size:.76rem;color:var(--vt-color-text-muted,#7f8aa3);word-break:break-word}',
'.search-snippet{margin-top:.25rem;font-size:.8rem;line-height:1.45;color:var(--vt-color-text-secondary,#b7c0d4);white-space:pre-wrap;overflow-wrap:anywhere}',
'.search-meta{margin-top:.28rem;font-size:.72rem;color:var(--vt-color-text-muted,#7f8aa3)}',
'.search-open-btn{align-self:center;white-space:nowrap;min-width:4.5rem;padding:.35rem .65rem}',
'@media(max-width:700px){.search-result{grid-template-columns:1fr}.search-toolbar{align-items:stretch}.search-btn{width:100%}.search-scope{max-width:none}}'
].join('\n');
@@ -54,7 +61,9 @@
Object.keys(attrs).forEach(function (k) {
if (attrs[k] == null) return;
if (k === 'className') elem.className = attrs[k];
else if (k === 'style' && typeof attrs[k] === 'object') Object.assign(elem.style, attrs[k]);
else if (k === 'style' && typeof attrs[k] === 'object') {
if (elem.style) Object.assign(elem.style, attrs[k]);
}
else if (k.slice(0, 2) === 'on') elem.addEventListener(k.slice(2).toLowerCase(), attrs[k]);
else if (k === 'textContent') elem.textContent = attrs[k];
else if (k === 'innerHTML') elem.innerHTML = attrs[k];
@@ -74,6 +83,20 @@
return String(path || '').split('/').filter(Boolean).join('/');
}
function baseName(path) {
path = cleanPath(path);
var parts = path.split('/');
return parts[parts.length - 1] || path || 'Untitled';
}
function resultTypeLabel(result) {
var type = String(result && result.type || 'result').toLowerCase();
if (type === 'folder') return 'Folder';
if (type === 'file') return 'File';
if (type === 'external') return 'Plugin';
return type.charAt(0).toUpperCase() + type.slice(1);
}
function extension(entry) {
var explicit = String((entry && entry.extension) || '').replace(/^\./, '').toLowerCase();
if (explicit) return explicit;
@@ -281,7 +304,7 @@
mount: function (containerEl, props, api) {
injectStyles();
var rootPath = cleanPath(props && (props.workspaceRootPath || props.workspaceName));
var state = { query: '', searching: false, results: [], status: 'Enter at least 2 characters.', error: '' };
var state = { query: '', searching: false, results: [], status: 'Enter at least 2 characters.', error: '', providerErrors: [] };
var searchTimer = null;
var searchSeq = 0;
var index = null;
@@ -290,6 +313,7 @@
var input = null;
var button = null;
var statusEl = null;
var alertEl = null;
var resultsEl = null;
function ensureLayout() {
@@ -322,6 +346,8 @@
statusEl = el('div', { className: 'search-status' });
containerEl.appendChild(statusEl);
alertEl = el('div', { className: 'search-alert', hidden: 'hidden' });
containerEl.appendChild(alertEl);
resultsEl = el('div', { className: 'search-results' });
containerEl.appendChild(resultsEl);
}
@@ -335,6 +361,16 @@
button.disabled = !!state.searching;
statusEl.className = 'search-status' + (state.error ? ' error' : '');
statusEl.textContent = state.error || state.status;
alertEl.innerHTML = '';
if (state.providerErrors && state.providerErrors.length) {
if (typeof alertEl.removeAttribute === 'function') alertEl.removeAttribute('hidden');
alertEl.appendChild(el('details', {}, [
el('summary', {}, ['Some plugin search providers did not respond']),
el('div', {}, [state.providerErrors.join('; ')])
]));
} else if (typeof alertEl.setAttribute === 'function') {
alertEl.setAttribute('hidden', 'hidden');
}
resultsEl.innerHTML = '';
if (!state.results.length) {
resultsEl.appendChild(el('div', { className: 'search-empty' }, [state.searching ? 'Searching...' : 'No results']));
@@ -343,6 +379,10 @@
state.results.forEach(function (result) {
resultsEl.appendChild(el('div', { className: 'search-result' }, [
el('div', {}, [
el('div', { className: 'search-result-head' }, [
el('span', { className: 'search-type' }, [resultTypeLabel(result)]),
el('span', { className: 'search-title', title: result.path }, [baseName(result.path)])
]),
el('div', { className: 'search-path' }, [result.path]),
el('div', { className: 'search-snippet' }, [result.snippet]),
el('div', { className: 'search-meta' }, [
@@ -350,7 +390,7 @@
])
]),
result.openable ? el('button', {
className: 'search-btn',
className: 'search-btn search-open-btn',
textContent: 'Open',
'data-search-open': result.path,
onClick: function () {
@@ -377,6 +417,7 @@
state.results = [];
state.status = 'Enter at least 2 characters.';
state.error = '';
state.providerErrors = [];
render();
return;
}
@@ -393,11 +434,13 @@
state.results = [];
state.status = 'Enter at least 2 characters.';
state.error = '';
state.providerErrors = [];
render();
return;
}
state.searching = true;
state.error = '';
state.providerErrors = [];
state.status = 'Searching...';
var seq = searchSeq + 1;
searchSeq = seq;
@@ -409,13 +452,12 @@
if (seq !== searchSeq) return;
state.results = results.concat(external.results);
state.status = state.results.length + ' result' + (state.results.length === 1 ? '' : 's');
if (external.errors.length) {
state.status += ' - ' + external.errors.join('; ');
}
state.providerErrors = external.errors;
} catch (err) {
if (seq !== searchSeq) return;
state.results = [];
state.error = err && err.message ? err.message : String(err);
state.providerErrors = [];
} finally {
if (seq !== searchSeq) return;
state.searching = false;