diff --git a/plugins/default-editor/frontend/src/index.js b/plugins/default-editor/frontend/src/index.js index bc3c0db..b2ab5fc 100644 --- a/plugins/default-editor/frontend/src/index.js +++ b/plugins/default-editor/frontend/src/index.js @@ -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-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-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-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}', @@ -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 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-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}', '@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'); @@ -303,9 +301,8 @@ containerEl.setAttribute('data-resource-path', resourcePath); 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 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 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; @@ -313,7 +310,7 @@ 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 statusEl = el('span', { className: 'de-status', 'data-save-state': '' }); - var toolbarChildren = [modeLabel, contextLabel]; + var toolbarChildren = [contextLabel]; if (notesBadge) toolbarChildren.push(notesBadge); toolbarChildren.push(spacer); [editBtn, previewBtn, splitBtn, reloadBtn, saveBtn, statusEl].forEach(function (node) { if (node) toolbarChildren.push(node); }); @@ -323,18 +320,18 @@ if (isMarkdown) { mdToolbar = el('div', { className: 'de-md-toolbar' }); [ - ['heading', 'H', 'Heading'], - ['bold', 'B', 'Bold'], - ['italic', 'I', 'Italic'], - ['link', 'Link', 'Link'], - ['code', 'Code', 'Inline code'], - ['code-block', '```', 'Code block'], - ['bullet', '• List', 'Bullet list'], - ['numbered', '1. List', 'Numbered list'], - ['quote', 'Quote', 'Quote'], - ['task', 'Task', 'Task item'] + ['heading', 'H', 'ui.md.heading', 'Heading'], + ['bold', 'B', 'ui.md.bold', 'Bold'], + ['italic', 'I', 'ui.md.italic', 'Italic'], + ['link', 'Link', 'ui.md.link', 'Link'], + ['code', 'Code', 'ui.md.inlineCode', 'Inline code'], + ['code-block', '```', 'ui.md.codeBlock', 'Code block'], + ['bullet', '• List', 'ui.md.bulletList', 'Bullet list'], + ['numbered', '1. List', 'ui.md.numberedList', 'Numbered list'], + ['quote', 'Quote', 'ui.md.quote', 'Quote'], + ['task', 'Task', 'ui.md.taskItem', 'Task 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); } @@ -342,10 +339,6 @@ var editorWrap = el('div', { className: 'de-editor-wrap' }); 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() { if (!linesEl || !textarea) return; var count = textarea.value.split('\n').length; @@ -543,12 +536,25 @@ reloadFromDisk(); var localeUnsubscribe = api.i18n && typeof 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 (previewBtn) previewBtn.textContent = tr('ui.preview', null, 'Preview'); if (splitBtn) splitBtn.textContent = tr('ui.split', null, 'Split'); reloadBtn.textContent = tr('ui.reload', null, 'Reload'); 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(); }) : null; diff --git a/plugins/default-editor/locales/en.json b/plugins/default-editor/locales/en.json index 3e5dc19..8b03758 100644 --- a/plugins/default-editor/locales/en.json +++ b/plugins/default-editor/locales/en.json @@ -5,6 +5,7 @@ "contributions.openProviders.verstak.default-editor.markdown.title": "Default Markdown Editor", "contributions.openProviders.verstak.default-editor.notes-markdown.title": "Default Notes Markdown Editor", "ui.notesContext": "notes context", + "ui.note": "Note", "ui.edit": "Edit", "ui.preview": "Preview", "ui.split": "Split", @@ -18,5 +19,15 @@ "ui.saved": "Saved", "ui.discardConfirm": "Discard unsaved changes and reload from disk?", "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" } diff --git a/plugins/default-editor/locales/ru.json b/plugins/default-editor/locales/ru.json index 48a58ce..a20a015 100644 --- a/plugins/default-editor/locales/ru.json +++ b/plugins/default-editor/locales/ru.json @@ -5,6 +5,7 @@ "contributions.openProviders.verstak.default-editor.markdown.title": "Стандартный редактор Markdown", "contributions.openProviders.verstak.default-editor.notes-markdown.title": "Стандартный редактор Markdown-заметок", "ui.notesContext": "контекст заметки", + "ui.note": "Заметка", "ui.edit": "Редактировать", "ui.preview": "Просмотр", "ui.split": "Разделить", @@ -18,5 +19,15 @@ "ui.saved": "Сохранено", "ui.discardConfirm": "Отменить несохранённые изменения и перечитать файл с диска?", "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": "Пункт задачи" } diff --git a/scripts/smoke-default-editor-plugin.js b/scripts/smoke-default-editor-plugin.js index 7f99f1c..17c127b 100755 --- a/scripts/smoke-default-editor-plugin.js +++ b/scripts/smoke-default-editor-plugin.js @@ -120,7 +120,7 @@ async function flush() { } } -async function mountEditor(secretProviderEnabled) { +async function mountEditor(secretProviderEnabled, translations) { const document = makeDocument(); const component = loadComponent(document); const opened = []; @@ -146,6 +146,15 @@ async function mountEditor(secretProviderEnabled) { 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'); 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'); 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'); 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'); @@ -182,6 +197,12 @@ async function mountEditor(secretProviderEnabled) { 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'); })().catch((err) => { console.error(err);