From 9eacd0013609852f22fe755d73730ff48a7dd4c6 Mon Sep 17 00:00:00 2001 From: sdarbinyan Date: Sun, 19 Jul 2026 08:48:22 +0400 Subject: [PATCH] feat(builder): modern grouped rich-text toolbar with icons and tooltips Co-Authored-By: Claude Fable 5 P0 user feedback: unstyled B/I/U/H2/List/1,2,3 buttons were not understandable for non-technical users. - Toolbar buttons grouped by intent (text style | headings | lists | insert | advanced) with visual separators, hover states, consistent 30px hit targets - Icons (PrimeIcons) for list/link/image/table/divider/code/embed; B/I/U keep their conventional letters but rendered in their own style (bold/italic/underline) as every mainstream editor does; every button gets a translated tooltip and aria-label (en/ru/hy) - Code view toggle moved to the right edge, visually de-emphasized - it is the expert path, not a primary action - Toolbar visually attaches to the editing surface (shared border, joined radius) so it reads as one control --- .../marketplace-html-editor.component.html | 29 ++++++++++-- .../marketplace-html-editor.component.scss | 47 ++++++++++++++++++- .../marketplace-html-editor.component.ts | 42 ++++++++++++----- src/app/i18n/en.ts | 13 +++++ src/app/i18n/hy.ts | 13 +++++ src/app/i18n/ru.ts | 13 +++++ src/app/i18n/translations.ts | 14 ++++++ 7 files changed, 153 insertions(+), 18 deletions(-) diff --git a/src/app/features/project-editor/components/html-editor/marketplace-html-editor.component.html b/src/app/features/project-editor/components/html-editor/marketplace-html-editor.component.html index 10b3428..810b8f7 100644 --- a/src/app/features/project-editor/components/html-editor/marketplace-html-editor.component.html +++ b/src/app/features/project-editor/components/html-editor/marketplace-html-editor.component.html @@ -1,9 +1,30 @@
-
- @for (item of toolbar; track item.id) { - + @if (showCode()) { diff --git a/src/app/features/project-editor/components/html-editor/marketplace-html-editor.component.scss b/src/app/features/project-editor/components/html-editor/marketplace-html-editor.component.scss index fd78843..850515d 100644 --- a/src/app/features/project-editor/components/html-editor/marketplace-html-editor.component.scss +++ b/src/app/features/project-editor/components/html-editor/marketplace-html-editor.component.scss @@ -7,9 +7,54 @@ .html-editor-toolbar { display: flex; flex-wrap: wrap; - gap: 0.25rem; + align-items: center; + gap: 8px; + padding: 6px; + border: 1px solid var(--border-color, #d3dad9); + border-bottom: none; + border-radius: var(--radius-sm) var(--radius-sm) 0 0; + background: var(--bg-secondary, #f7f8f8); } +.html-editor-toolbar__group { + display: flex; + gap: 2px; + + & + .html-editor-toolbar__group:not(.html-editor-toolbar__group--end) { + padding-left: 8px; + border-left: 1px solid var(--border-color, #d3dad9); + } +} + +.html-editor-toolbar__group--end { + margin-left: auto; +} + +.html-editor-toolbar__btn { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 30px; + height: 30px; + padding: 0 6px; + border: none; + border-radius: var(--radius-sm); + background: transparent; + color: var(--text-primary, #1e3c38); + font-size: 0.85rem; + cursor: pointer; + + .pi { font-size: 0.85rem; } + + &:hover { background: var(--bg-tertiary, #e8ecea); } + &:focus-visible { outline: 2px solid var(--color-primary, #2f8f5b); outline-offset: 1px; } +} + +.html-editor-toolbar__btn--bold { font-weight: 700; } +.html-editor-toolbar__btn--italic { font-style: italic; font-family: serif; } +.html-editor-toolbar__btn--underline { text-decoration: underline; } +.html-editor-toolbar__btn--wide { padding: 0 10px; font-size: 0.8rem; color: var(--text-secondary, #6b7280); } + .html-editor-surface { min-height: 160px; border: 1px solid var(--border, #ccc); diff --git a/src/app/features/project-editor/components/html-editor/marketplace-html-editor.component.ts b/src/app/features/project-editor/components/html-editor/marketplace-html-editor.component.ts index 6b236cb..2f4f41e 100644 --- a/src/app/features/project-editor/components/html-editor/marketplace-html-editor.component.ts +++ b/src/app/features/project-editor/components/html-editor/marketplace-html-editor.component.ts @@ -9,22 +9,35 @@ export interface HtmlEditorToolbarCommand { label: string; command: string; value?: string; + /** PrimeIcons class (e.g. 'pi-list'); when set the icon renders instead of the label. */ + icon?: string; + /** i18n key for the tooltip/aria-label. */ + titleKey?: string; } export const HTML_EDITOR_TOOLBAR: HtmlEditorToolbarCommand[] = [ - { id: 'bold', label: 'B', command: 'bold' }, - { id: 'italic', label: 'I', command: 'italic' }, - { id: 'underline', label: 'U', command: 'underline' }, - { id: 'h2', label: 'H2', command: 'formatBlock', value: 'H2' }, - { id: 'h3', label: 'H3', command: 'formatBlock', value: 'H3' }, - { id: 'ul', label: 'List', command: 'insertUnorderedList' }, - { id: 'ol', label: '1,2,3', command: 'insertOrderedList' }, - { id: 'link', label: 'Link', command: 'createLink' }, - { id: 'image', label: 'Image', command: 'insertImage' }, - { id: 'table', label: 'Table', command: 'insertHTML', value: '
  
' }, - { id: 'hr', label: 'HR', command: 'insertHorizontalRule' }, - { id: 'codeblock', label: 'Code', command: 'formatBlock', value: 'PRE' }, - { id: 'embed', label: 'Embed', command: 'insertEmbed' }, + { id: 'bold', label: 'B', command: 'bold', titleKey: 'builder.htmlToolBold' }, + { id: 'italic', label: 'I', command: 'italic', titleKey: 'builder.htmlToolItalic' }, + { id: 'underline', label: 'U', command: 'underline', titleKey: 'builder.htmlToolUnderline' }, + { id: 'h2', label: 'H2', command: 'formatBlock', value: 'H2', titleKey: 'builder.htmlToolHeading' }, + { id: 'h3', label: 'H3', command: 'formatBlock', value: 'H3', titleKey: 'builder.htmlToolSubheading' }, + { id: 'ul', label: '', icon: 'pi-list', command: 'insertUnorderedList', titleKey: 'builder.htmlToolBulletList' }, + { id: 'ol', label: '1.', command: 'insertOrderedList', titleKey: 'builder.htmlToolNumberedList' }, + { id: 'link', label: '', icon: 'pi-link', command: 'createLink', titleKey: 'builder.htmlToolLink' }, + { id: 'image', label: '', icon: 'pi-image', command: 'insertImage', titleKey: 'builder.htmlToolImage' }, + { id: 'table', label: '', icon: 'pi-table', command: 'insertHTML', value: '
  
', titleKey: 'builder.htmlToolTable' }, + { id: 'hr', label: '', icon: 'pi-minus', command: 'insertHorizontalRule', titleKey: 'builder.htmlToolDivider' }, + { id: 'codeblock', label: '', icon: 'pi-code', command: 'formatBlock', value: 'PRE', titleKey: 'builder.htmlToolCodeBlock' }, + { id: 'embed', label: '', icon: 'pi-video', command: 'insertEmbed', titleKey: 'builder.htmlToolEmbed' }, +]; + +/** Visual grouping for the toolbar: text style | headings | lists | insert | advanced. */ +export const HTML_EDITOR_TOOLBAR_GROUPS: string[][] = [ + ['bold', 'italic', 'underline'], + ['h2', 'h3'], + ['ul', 'ol'], + ['link', 'image', 'table', 'hr'], + ['codeblock', 'embed'], ]; @Component({ @@ -43,6 +56,9 @@ export class MarketplaceHtmlEditorComponent implements OnChanges, AfterViewInit @ViewChild('surface', { static: true }) surface!: ElementRef; readonly toolbar = HTML_EDITOR_TOOLBAR; + readonly toolbarGroups = HTML_EDITOR_TOOLBAR_GROUPS.map(group => + group.map(id => HTML_EDITOR_TOOLBAR.find(item => item.id === id)!).filter(Boolean) + ); readonly showCode = signal(false); readonly codeValue = signal(''); readonly codeError = signal(null); diff --git a/src/app/i18n/en.ts b/src/app/i18n/en.ts index 347ab34..aa60274 100644 --- a/src/app/i18n/en.ts +++ b/src/app/i18n/en.ts @@ -687,6 +687,19 @@ export const en: Translations = { linkVisibleDesc: 'Whether this navigation link is shown to shoppers.', appName: 'Marketplace Builder', backToDashboard: 'Back to dashboard', + htmlToolBold: 'Bold', + htmlToolItalic: 'Italic', + htmlToolUnderline: 'Underline', + htmlToolHeading: 'Heading', + htmlToolSubheading: 'Subheading', + htmlToolBulletList: 'Bulleted list', + htmlToolNumberedList: 'Numbered list', + htmlToolLink: 'Insert link', + htmlToolImage: 'Insert image', + htmlToolTable: 'Insert table', + htmlToolDivider: 'Divider line', + htmlToolCodeBlock: 'Code block', + htmlToolEmbed: 'Embed video', breadcrumbRoot: 'Marketplace Builder', statusComplete: 'Complete', statusInProgress: 'In progress', diff --git a/src/app/i18n/hy.ts b/src/app/i18n/hy.ts index 130dda0..427646b 100644 --- a/src/app/i18n/hy.ts +++ b/src/app/i18n/hy.ts @@ -687,6 +687,19 @@ export const hy: Translations = { linkVisibleDesc: 'Արդյո՞ք այս նավիգացիոն հղումը ցուցադրվում է գնորդներին։', appName: 'Մարկետփլեյսի կոնստրուկտոր', backToDashboard: 'Վերադառնալ կառավարման վահանակ', + htmlToolBold: 'Թավ', + htmlToolItalic: 'Շեղ', + htmlToolUnderline: 'Ընդգծված', + htmlToolHeading: 'Վերնագիր', + htmlToolSubheading: 'Ենթավերնագիր', + htmlToolBulletList: 'Կետային ցուցակ', + htmlToolNumberedList: 'Համարակալված ցուցակ', + htmlToolLink: 'Տեղադրել հղում', + htmlToolImage: 'Տեղադրել նկար', + htmlToolTable: 'Տեղադրել աղյուսակ', + htmlToolDivider: 'Բաժանարար գիծ', + htmlToolCodeBlock: 'Կոդի բլոկ', + htmlToolEmbed: 'Ներդնել տեսանյութ', breadcrumbRoot: 'Մարկետփլեյսի կոնստրուկտոր', statusComplete: 'Ավարտված', statusInProgress: 'Ընթացքի մեջ', diff --git a/src/app/i18n/ru.ts b/src/app/i18n/ru.ts index 0840872..e5f93e1 100644 --- a/src/app/i18n/ru.ts +++ b/src/app/i18n/ru.ts @@ -687,6 +687,19 @@ export const ru: Translations = { linkVisibleDesc: 'Показывать ли эту ссылку навигации покупателям.', appName: 'Конструктор маркетплейса', backToDashboard: 'Назад в панель управления', + htmlToolBold: 'Жирный', + htmlToolItalic: 'Курсив', + htmlToolUnderline: 'Подчёркнутый', + htmlToolHeading: 'Заголовок', + htmlToolSubheading: 'Подзаголовок', + htmlToolBulletList: 'Маркированный список', + htmlToolNumberedList: 'Нумерованный список', + htmlToolLink: 'Вставить ссылку', + htmlToolImage: 'Вставить изображение', + htmlToolTable: 'Вставить таблицу', + htmlToolDivider: 'Разделительная линия', + htmlToolCodeBlock: 'Блок кода', + htmlToolEmbed: 'Встроить видео', breadcrumbRoot: 'Конструктор маркетплейса', statusComplete: 'Готово', statusInProgress: 'В процессе', diff --git a/src/app/i18n/translations.ts b/src/app/i18n/translations.ts index 9843855..c9edf5a 100644 --- a/src/app/i18n/translations.ts +++ b/src/app/i18n/translations.ts @@ -685,6 +685,20 @@ export interface Translations { linkVisibleDesc: string; appName: string; backToDashboard: string; + htmlToolBold: string; + htmlToolItalic: string; + htmlToolUnderline: string; + htmlToolHeading: string; + htmlToolSubheading: string; + htmlToolBulletList: string; + htmlToolNumberedList: string; + htmlToolLink: string; + htmlToolImage: string; + htmlToolTable: string; + htmlToolDivider: string; + htmlToolCodeBlock: string; + htmlToolEmbed: string; + breadcrumbRoot: string; statusComplete: string; statusInProgress: string;