fix: simplify markdown editor chrome

This commit is contained in:
mirivlad 2026-07-14 22:56:40 +08:00
parent 90ad8e8467
commit b449e9c616
4 changed files with 73 additions and 24 deletions

View File

@ -18,7 +18,6 @@
'.de-root{display:flex;flex-direction:column;height:100%;min-height:0;overflow:hidden;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",system-ui,sans-serif;color:#e0e0e0;background:#0d0d1a}', '.de-root{display:flex;flex-direction:column;height:100%;min-height:0;overflow:hidden;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",system-ui,sans-serif;color:#e0e0e0;background:#0d0d1a}',
'.de-toolbar,.de-md-toolbar{display:flex;align-items:center;gap:.45rem;padding:.45rem .75rem;border-bottom:1px solid #16213e;flex-shrink:0;background:#12122a;flex-wrap:wrap}', '.de-toolbar,.de-md-toolbar{display:flex;align-items:center;gap:.45rem;padding:.45rem .75rem;border-bottom:1px solid #16213e;flex-shrink:0;background:#12122a;flex-wrap:wrap}',
'.de-md-toolbar{background:#101028;padding:.38rem .75rem}', '.de-md-toolbar{background:#101028;padding:.38rem .75rem}',
'.de-toolbar-mode{font-size:.75rem;color:#4ecca3;padding:.15rem .5rem;border-radius:3px;background:#1a2a3a}',
'.de-toolbar-context{font-size:.75rem;color:#a0a0bb;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}', '.de-toolbar-context{font-size:.75rem;color:#a0a0bb;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}',
'.de-toolbar-spacer{flex:1}', '.de-toolbar-spacer{flex:1}',
'.de-toolbar-btn,.de-md-btn{font-size:.75rem;padding:.28rem .58rem;border:1px solid #333;border-radius:4px;background:#1a1a2e;color:#ccc;cursor:pointer}', '.de-toolbar-btn,.de-md-btn{font-size:.75rem;padding:.28rem .58rem;border:1px solid #333;border-radius:4px;background:#1a1a2e;color:#ccc;cursor:pointer}',
@ -45,7 +44,6 @@
'.de-preview table{border-collapse:collapse;margin:.8rem 0;max-width:100%;display:block;overflow:auto}.de-preview th,.de-preview td{border:1px solid #333;padding:.35rem .6rem;text-align:left}.de-preview th{background:#1a1a2e}', '.de-preview table{border-collapse:collapse;margin:.8rem 0;max-width:100%;display:block;overflow:auto}.de-preview th,.de-preview td{border:1px solid #333;padding:.35rem .6rem;text-align:left}.de-preview th{background:#1a1a2e}',
'.de-preview img{max-width:100%;height:auto;border-radius:4px}.de-preview .task{margin-right:.4rem}', '.de-preview img{max-width:100%;height:auto;border-radius:4px}.de-preview .task{margin-right:.4rem}',
'.de-notes-badge{font-size:.65rem;padding:.1rem .4rem;border-radius:3px;background:#2a1a3a;color:#b388ff}', '.de-notes-badge{font-size:.65rem;padding:.1rem .4rem;border-radius:3px;background:#2a1a3a;color:#b388ff}',
'.de-notes-info{padding:.45rem .75rem;background:#111126;border-top:1px solid #16213e;font-size:.75rem;color:#8b8ba8;flex-shrink:0}',
'.de-loading,.de-error{flex:1;display:flex;align-items:center;justify-content:center;color:#777;padding:2rem}.de-error{color:#e74c3c;flex-direction:column;gap:.5rem}.de-error-msg{font-size:.85rem;color:#aaa;max-width:420px;text-align:center}', '.de-loading,.de-error{flex:1;display:flex;align-items:center;justify-content:center;color:#777;padding:2rem}.de-error{color:#e74c3c;flex-direction:column;gap:.5rem}.de-error-msg{font-size:.85rem;color:#aaa;max-width:420px;text-align:center}',
'@media(max-width:780px){.de-editor-wrap{flex-direction:column}.de-pane+.de-pane{border-left:0;border-top:1px solid #16213e}.de-toolbar-context{max-width:100%}}' '@media(max-width:780px){.de-editor-wrap{flex-direction:column}.de-pane+.de-pane{border-left:0;border-top:1px solid #16213e}.de-toolbar-context{max-width:100%}}'
].join('\n'); ].join('\n');
@ -303,9 +301,8 @@
containerEl.setAttribute('data-resource-path', resourcePath); containerEl.setAttribute('data-resource-path', resourcePath);
containerEl.setAttribute('data-request-mode', requestedMode); containerEl.setAttribute('data-request-mode', requestedMode);
var modeLabel = el('span', { className: 'de-toolbar-mode' }, [editorMode]);
var contextLabel = el('span', { className: 'de-toolbar-context', title: resourcePath }, [resourcePath || fileName(resourcePath)]); var contextLabel = el('span', { className: 'de-toolbar-context', title: resourcePath }, [resourcePath || fileName(resourcePath)]);
var notesBadge = editorMode === 'notes-markdown' ? el('span', { className: 'de-notes-badge', 'data-notes-badge': '' }, [tr('ui.notesContext', null, 'notes context')]) : null; var notesBadge = editorMode === 'notes-markdown' ? el('span', { className: 'de-notes-badge', 'data-notes-badge': '' }, [tr('ui.note', null, 'Note')]) : null;
var spacer = el('span', { className: 'de-toolbar-spacer' }); var spacer = el('span', { className: 'de-toolbar-spacer' });
var editBtn = isMarkdown ? el('button', { className: 'de-toolbar-btn', 'data-editor-mode-button': 'edit' }, [tr('ui.edit', null, 'Edit')]) : null; var editBtn = isMarkdown ? el('button', { className: 'de-toolbar-btn', 'data-editor-mode-button': 'edit' }, [tr('ui.edit', null, 'Edit')]) : null;
var previewBtn = isMarkdown ? el('button', { className: 'de-toolbar-btn', 'data-editor-mode-button': 'preview' }, [tr('ui.preview', null, 'Preview')]) : null; var previewBtn = isMarkdown ? el('button', { className: 'de-toolbar-btn', 'data-editor-mode-button': 'preview' }, [tr('ui.preview', null, 'Preview')]) : null;
@ -313,7 +310,7 @@
var reloadBtn = el('button', { className: 'de-toolbar-btn', 'data-editor-action': 'reload' }, [tr('ui.reload', null, 'Reload')]); var reloadBtn = el('button', { className: 'de-toolbar-btn', 'data-editor-action': 'reload' }, [tr('ui.reload', null, 'Reload')]);
var saveBtn = el('button', { className: 'de-toolbar-btn', 'data-editor-action': 'save' }, [tr('ui.save', null, 'Save')]); var saveBtn = el('button', { className: 'de-toolbar-btn', 'data-editor-action': 'save' }, [tr('ui.save', null, 'Save')]);
var statusEl = el('span', { className: 'de-status', 'data-save-state': '' }); var statusEl = el('span', { className: 'de-status', 'data-save-state': '' });
var toolbarChildren = [modeLabel, contextLabel]; var toolbarChildren = [contextLabel];
if (notesBadge) toolbarChildren.push(notesBadge); if (notesBadge) toolbarChildren.push(notesBadge);
toolbarChildren.push(spacer); toolbarChildren.push(spacer);
[editBtn, previewBtn, splitBtn, reloadBtn, saveBtn, statusEl].forEach(function (node) { if (node) toolbarChildren.push(node); }); [editBtn, previewBtn, splitBtn, reloadBtn, saveBtn, statusEl].forEach(function (node) { if (node) toolbarChildren.push(node); });
@ -323,18 +320,18 @@
if (isMarkdown) { if (isMarkdown) {
mdToolbar = el('div', { className: 'de-md-toolbar' }); mdToolbar = el('div', { className: 'de-md-toolbar' });
[ [
['heading', 'H', 'Heading'], ['heading', 'H', 'ui.md.heading', 'Heading'],
['bold', 'B', 'Bold'], ['bold', 'B', 'ui.md.bold', 'Bold'],
['italic', 'I', 'Italic'], ['italic', 'I', 'ui.md.italic', 'Italic'],
['link', 'Link', 'Link'], ['link', 'Link', 'ui.md.link', 'Link'],
['code', 'Code', 'Inline code'], ['code', 'Code', 'ui.md.inlineCode', 'Inline code'],
['code-block', '```', 'Code block'], ['code-block', '```', 'ui.md.codeBlock', 'Code block'],
['bullet', '• List', 'Bullet list'], ['bullet', '• List', 'ui.md.bulletList', 'Bullet list'],
['numbered', '1. List', 'Numbered list'], ['numbered', '1. List', 'ui.md.numberedList', 'Numbered list'],
['quote', 'Quote', 'Quote'], ['quote', 'Quote', 'ui.md.quote', 'Quote'],
['task', 'Task', 'Task item'] ['task', 'Task', 'ui.md.taskItem', 'Task item']
].forEach(function (item) { ].forEach(function (item) {
mdToolbar.appendChild(el('button', { className: 'de-md-btn', 'data-md-action': item[0], title: item[2] }, [item[1]])); mdToolbar.appendChild(el('button', { className: 'de-md-btn', type: 'button', 'data-md-action': item[0], title: tr(item[2], null, item[3]), 'aria-label': tr(item[2], null, item[3]) }, [item[1]]));
}); });
containerEl.appendChild(mdToolbar); containerEl.appendChild(mdToolbar);
} }
@ -342,10 +339,6 @@
var editorWrap = el('div', { className: 'de-editor-wrap' }); var editorWrap = el('div', { className: 'de-editor-wrap' });
containerEl.appendChild(editorWrap); containerEl.appendChild(editorWrap);
if (editorMode === 'notes-markdown') {
containerEl.appendChild(el('div', { className: 'de-notes-info' }, [tr('ui.notesInfo', null, 'Notes context active. Note actions, backlinks, and graph tools are reserved for the future Notes plugin.')]));
}
function updateLineNumbers() { function updateLineNumbers() {
if (!linesEl || !textarea) return; if (!linesEl || !textarea) return;
var count = textarea.value.split('\n').length; var count = textarea.value.split('\n').length;
@ -543,12 +536,25 @@
reloadFromDisk(); reloadFromDisk();
var localeUnsubscribe = api.i18n && typeof api.i18n.onDidChangeLocale === 'function' var localeUnsubscribe = api.i18n && typeof api.i18n.onDidChangeLocale === 'function'
? api.i18n.onDidChangeLocale(function () { ? api.i18n.onDidChangeLocale(function () {
if (notesBadge) notesBadge.textContent = tr('ui.notesContext', null, 'notes context'); if (notesBadge) notesBadge.textContent = tr('ui.note', null, 'Note');
if (editBtn) editBtn.textContent = tr('ui.edit', null, 'Edit'); if (editBtn) editBtn.textContent = tr('ui.edit', null, 'Edit');
if (previewBtn) previewBtn.textContent = tr('ui.preview', null, 'Preview'); if (previewBtn) previewBtn.textContent = tr('ui.preview', null, 'Preview');
if (splitBtn) splitBtn.textContent = tr('ui.split', null, 'Split'); if (splitBtn) splitBtn.textContent = tr('ui.split', null, 'Split');
reloadBtn.textContent = tr('ui.reload', null, 'Reload'); reloadBtn.textContent = tr('ui.reload', null, 'Reload');
saveBtn.textContent = tr('ui.save', null, 'Save'); saveBtn.textContent = tr('ui.save', null, 'Save');
if (mdToolbar) {
[
['heading', 'ui.md.heading', 'Heading'], ['bold', 'ui.md.bold', 'Bold'], ['italic', 'ui.md.italic', 'Italic'],
['link', 'ui.md.link', 'Link'], ['code', 'ui.md.inlineCode', 'Inline code'], ['code-block', 'ui.md.codeBlock', 'Code block'],
['bullet', 'ui.md.bulletList', 'Bullet list'], ['numbered', 'ui.md.numberedList', 'Numbered list'], ['quote', 'ui.md.quote', 'Quote'], ['task', 'ui.md.taskItem', 'Task item']
].forEach(function (item) {
var button = mdToolbar.querySelector('[data-md-action="' + item[0] + '"]');
if (!button) return;
var label = tr(item[1], null, item[2]);
button.setAttribute('title', label);
button.setAttribute('aria-label', label);
});
}
updateStatus(); updateStatus();
}) })
: null; : null;

View File

@ -5,6 +5,7 @@
"contributions.openProviders.verstak.default-editor.markdown.title": "Default Markdown Editor", "contributions.openProviders.verstak.default-editor.markdown.title": "Default Markdown Editor",
"contributions.openProviders.verstak.default-editor.notes-markdown.title": "Default Notes Markdown Editor", "contributions.openProviders.verstak.default-editor.notes-markdown.title": "Default Notes Markdown Editor",
"ui.notesContext": "notes context", "ui.notesContext": "notes context",
"ui.note": "Note",
"ui.edit": "Edit", "ui.edit": "Edit",
"ui.preview": "Preview", "ui.preview": "Preview",
"ui.split": "Split", "ui.split": "Split",
@ -18,5 +19,15 @@
"ui.saved": "Saved", "ui.saved": "Saved",
"ui.discardConfirm": "Discard unsaved changes and reload from disk?", "ui.discardConfirm": "Discard unsaved changes and reload from disk?",
"ui.loading": "Loading...", "ui.loading": "Loading...",
"ui.loadFailed": "Could not load the file. Please try again." "ui.loadFailed": "Could not load the file. Please try again.",
"ui.md.heading": "Heading",
"ui.md.bold": "Bold",
"ui.md.italic": "Italic",
"ui.md.link": "Link",
"ui.md.inlineCode": "Inline code",
"ui.md.codeBlock": "Code block",
"ui.md.bulletList": "Bullet list",
"ui.md.numberedList": "Numbered list",
"ui.md.quote": "Quote",
"ui.md.taskItem": "Task item"
} }

View File

@ -5,6 +5,7 @@
"contributions.openProviders.verstak.default-editor.markdown.title": "Стандартный редактор Markdown", "contributions.openProviders.verstak.default-editor.markdown.title": "Стандартный редактор Markdown",
"contributions.openProviders.verstak.default-editor.notes-markdown.title": "Стандартный редактор Markdown-заметок", "contributions.openProviders.verstak.default-editor.notes-markdown.title": "Стандартный редактор Markdown-заметок",
"ui.notesContext": "контекст заметки", "ui.notesContext": "контекст заметки",
"ui.note": "Заметка",
"ui.edit": "Редактировать", "ui.edit": "Редактировать",
"ui.preview": "Просмотр", "ui.preview": "Просмотр",
"ui.split": "Разделить", "ui.split": "Разделить",
@ -18,5 +19,15 @@
"ui.saved": "Сохранено", "ui.saved": "Сохранено",
"ui.discardConfirm": "Отменить несохранённые изменения и перечитать файл с диска?", "ui.discardConfirm": "Отменить несохранённые изменения и перечитать файл с диска?",
"ui.loading": "Загрузка...", "ui.loading": "Загрузка...",
"ui.loadFailed": "Не удалось загрузить файл. Повторите попытку." "ui.loadFailed": "Не удалось загрузить файл. Повторите попытку.",
"ui.md.heading": "Заголовок",
"ui.md.bold": "Жирный текст",
"ui.md.italic": "Курсив",
"ui.md.link": "Ссылка",
"ui.md.inlineCode": "Встроенный код",
"ui.md.codeBlock": "Блок кода",
"ui.md.bulletList": "Маркированный список",
"ui.md.numberedList": "Нумерованный список",
"ui.md.quote": "Цитата",
"ui.md.taskItem": "Пункт задачи"
} }

View File

@ -120,7 +120,7 @@ async function flush() {
} }
} }
async function mountEditor(secretProviderEnabled) { async function mountEditor(secretProviderEnabled, translations) {
const document = makeDocument(); const document = makeDocument();
const component = loadComponent(document); const component = loadComponent(document);
const opened = []; const opened = [];
@ -146,6 +146,15 @@ async function mountEditor(secretProviderEnabled) {
return { status: 'opened', request }; return { status: 'opened', request };
}, },
}, },
i18n: {
t: (key, params, fallback) => {
let value = translations && translations[key] ? translations[key] : (fallback || key);
Object.entries(params || {}).forEach(([name, replacement]) => {
value = value.replace(`{${name}}`, String(replacement));
});
return value;
},
},
}; };
const container = document.createElement('div'); const container = document.createElement('div');
component.mount(container, { component.mount(container, {
@ -162,6 +171,12 @@ async function mountEditor(secretProviderEnabled) {
if (disabledPreview.innerHTML.includes('data-secret-id')) throw new Error('secret link rendered without secrets provider'); if (disabledPreview.innerHTML.includes('data-secret-id')) throw new Error('secret link rendered without secrets provider');
const enabled = await mountEditor(true); const enabled = await mountEditor(true);
if (enabled.container.textContent.includes('notes-markdown') || enabled.container.textContent.includes('generic-markdown')) {
throw new Error('editor must not expose the technical editor mode in its toolbar');
}
if (enabled.container.textContent.includes('Notes context active')) {
throw new Error('editor must not show an implementation roadmap in the notes UI');
}
const preview = walk(enabled.container, (node) => node.className === 'de-preview'); const preview = walk(enabled.container, (node) => node.className === 'de-preview');
if (!preview) throw new Error('enabled preview missing'); if (!preview) throw new Error('enabled preview missing');
if (!preview.innerHTML.includes('data-secret-id="client-a.db"')) throw new Error('secret link did not render with provider'); if (!preview.innerHTML.includes('data-secret-id="client-a.db"')) throw new Error('secret link did not render with provider');
@ -182,6 +197,12 @@ async function mountEditor(secretProviderEnabled) {
throw new Error('secret link did not open through workbench'); throw new Error('secret link did not open through workbench');
} }
const russian = await mountEditor(true, { 'ui.md.heading': 'Заголовок' });
const headingButton = walk(russian.container, (node) => node.getAttribute && node.getAttribute('data-md-action') === 'heading');
if (!headingButton || headingButton.getAttribute('title') !== 'Заголовок') {
throw new Error('Markdown toolbar titles must use the locale catalog');
}
console.log('default editor smoke passed'); console.log('default editor smoke passed');
})().catch((err) => { })().catch((err) => {
console.error(err); console.error(err);