From 65c6d6f5d1879a48ec320aa5609e2d0516d65848 Mon Sep 17 00:00:00 2001 From: sdarbinyan Date: Mon, 27 Jul 2026 11:12:35 +0400 Subject: [PATCH] feat: Page Editor UX Phase 2 - unsaved changes panel, property search, reset property, empty states - ProjectEditorFacade.resetField(key): field-level revert-to-original, reusing getByPath + a new immutable setByPath (write-side counterpart, scalar/object dot-paths only). - app-form-field gains showReset/resetLabel/(resetClicked) - a reusable per-field reset affordance, wired on branding logo/title and theme primary/background color. - Save bar: unsaved-changes count is now clickable, expanding a field-level diff list (reuses facade.changeSummary(), already built for the Preview tab) with jump-to-section links. - BuilderPropertySearchComponent: filters EditorSchemaService.all() by translated label/hint, jumps to the owning section - no new registry, reuses the existing schema. - navigation-section: app-empty-state (existing component) added for empty header/footer link lists. - Reset Section and Draft/Published badge were already implemented; not touched. - New copy added as translation keys (en/ru/hy). --- .../builder-property-search.component.html | 32 +++++++ .../builder-property-search.component.scss | 76 +++++++++++++++ .../builder-property-search.component.ts | 92 +++++++++++++++++++ .../project-editor-save-bar.component.html | 16 +++- .../project-editor-save-bar.component.scss | 56 +++++++++++ .../project-editor-save-bar.component.ts | 10 +- .../facade/project-editor.facade.ts | 28 ++++++ .../pages/project-editor-page.component.html | 1 + .../pages/project-editor-page.component.scss | 5 + .../pages/project-editor-page.component.ts | 2 + .../sections/branding-section.component.html | 6 ++ .../sections/branding-section.component.ts | 4 + .../navigation-section.component.html | 6 ++ .../sections/navigation-section.component.ts | 3 +- .../sections/theme-section.component.html | 6 ++ .../sections/theme-section.component.ts | 4 + src/app/i18n/en.ts | 10 ++ src/app/i18n/hy.ts | 10 ++ src/app/i18n/ru.ts | 10 ++ src/app/i18n/translations.ts | 10 ++ .../ui/form-field/form-field.component.html | 18 +++- .../ui/form-field/form-field.component.scss | 28 ++++++ .../ui/form-field/form-field.component.ts | 8 +- 23 files changed, 432 insertions(+), 9 deletions(-) create mode 100644 src/app/features/project-editor/components/property-search/builder-property-search.component.html create mode 100644 src/app/features/project-editor/components/property-search/builder-property-search.component.scss create mode 100644 src/app/features/project-editor/components/property-search/builder-property-search.component.ts diff --git a/src/app/features/project-editor/components/property-search/builder-property-search.component.html b/src/app/features/project-editor/components/property-search/builder-property-search.component.html new file mode 100644 index 0000000..9ee4d10 --- /dev/null +++ b/src/app/features/project-editor/components/property-search/builder-property-search.component.html @@ -0,0 +1,32 @@ + diff --git a/src/app/features/project-editor/components/property-search/builder-property-search.component.scss b/src/app/features/project-editor/components/property-search/builder-property-search.component.scss new file mode 100644 index 0000000..1e3bb83 --- /dev/null +++ b/src/app/features/project-editor/components/property-search/builder-property-search.component.scss @@ -0,0 +1,76 @@ +.builder-property-search { + position: relative; +} + +.builder-property-search__label { + position: absolute; + left: var(--space-sm, 0.5rem); + top: 50%; + transform: translateY(-50%); + display: flex; + color: var(--text-secondary, #6b7280); + pointer-events: none; +} + +.builder-property-search__input { + width: 100%; + height: 36px; + padding: 0 var(--space-sm, 0.5rem) 0 32px; + border: 1px solid var(--border-color, #e4e4e7); + border-radius: var(--radius-md, 6px); + background: var(--bg-primary, #fff); + color: var(--text-primary, #1f322d); + font-family: inherit; + font-size: var(--font-size-sm, 0.8125rem); + + &:focus-visible { + outline: none; + border-color: var(--primary-color, #2f6e5d); + box-shadow: 0 0 0 2px color-mix(in srgb, var(--primary-color, #2f6e5d) 25%, transparent); + } +} + +.builder-property-search__results { + position: absolute; + z-index: 50; + top: calc(100% + 4px); + left: 0; + right: 0; + margin: 0; + padding: var(--space-xs, 0.25rem); + list-style: none; + background: var(--bg-primary, #fff); + border: 1px solid var(--border-color, #e4e4e7); + border-radius: var(--radius-md, 6px); + box-shadow: var(--shadow-md, 0 4px 12px rgba(0, 0, 0, 0.08)); + max-height: 300px; + overflow-y: auto; + + a { + display: flex; + justify-content: space-between; + gap: var(--space-sm, 0.5rem); + padding: var(--space-xs, 0.25rem) var(--space-sm, 0.5rem); + border-radius: var(--radius-sm, 4px); + color: var(--text-primary, #1f322d); + text-decoration: none; + font-size: var(--font-size-sm, 0.8125rem); + + &:hover, + &:focus-visible { + background: var(--bg-secondary, #f4f4f5); + outline: none; + } + } +} + +.builder-property-search__result-section { + color: var(--text-secondary, #6b7280); + font-size: var(--font-size-xs, 0.75rem); +} + +.builder-property-search__empty { + padding: var(--space-xs, 0.25rem) var(--space-sm, 0.5rem); + color: var(--text-secondary, #6b7280); + font-size: var(--font-size-sm, 0.8125rem); +} diff --git a/src/app/features/project-editor/components/property-search/builder-property-search.component.ts b/src/app/features/project-editor/components/property-search/builder-property-search.component.ts new file mode 100644 index 0000000..b7013c8 --- /dev/null +++ b/src/app/features/project-editor/components/property-search/builder-property-search.component.ts @@ -0,0 +1,92 @@ +import { ChangeDetectionStrategy, Component, computed, inject, signal } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { EditorSchemaService } from '../../schema/editor-schema.service'; +import { TranslatePipe } from '../../../../i18n/translate.pipe'; +import { TranslateService } from '../../../../i18n/translate.service'; +import { LangRoutePipe } from '../../../../pipes/lang-route.pipe'; +import { IconComponent } from '../../../../shared/ui/icon/icon.component'; +import { RouterLink } from '@angular/router'; + +interface PropertySearchResult { + key: string; + label: string; + hint: string; + sectionId: string; + sectionLabel: string; +} + +const SECTION_LABEL_KEYS: Record = { + general: 'builder.general', + branding: 'builder.branding', + theme: 'builder.theme', + header: 'builder.header', + footer: 'builder.footer', + homepage: 'builder.homepage', + widgets: 'builder.widgets', + 'static-pages': 'builder.staticPages', + features: 'builder.marketplaceFeatures', + languages: 'builder.languagesTab', + navigation: 'builder.navigationTab', + preview: 'builder.preview', +}; + +/** + * Filters the field-schema registry (EditorSchemaService.all(), the single source of + * truth already used for validation/readiness) by translated label/hint text, so a + * non-technical admin can find "where do I change X" without knowing which section + * it lives in. Selecting a result jumps to that field's section. + */ +@Component({ + selector: 'app-builder-property-search', + standalone: true, + imports: [FormsModule, TranslatePipe, RouterLink, LangRoutePipe, IconComponent], + templateUrl: './builder-property-search.component.html', + styleUrl: './builder-property-search.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class BuilderPropertySearchComponent { + private readonly schema = inject(EditorSchemaService); + private readonly translate = inject(TranslateService); + + readonly query = signal(''); + readonly open = signal(false); + + private readonly allResults = computed(() => + this.schema.all().map(field => ({ + key: field.key, + label: this.translate.t(field.labelKey), + hint: field.hintKey ? this.translate.t(field.hintKey) : '', + sectionId: field.section, + sectionLabel: this.translate.t(SECTION_LABEL_KEYS[field.section] ?? field.section), + })) + ); + + readonly results = computed(() => { + const term = this.query().trim().toLowerCase(); + if (!term) { + return []; + } + return this.allResults() + .filter(result => + result.label.toLowerCase().includes(term) || + result.hint.toLowerCase().includes(term) || + result.sectionLabel.toLowerCase().includes(term) + ) + .slice(0, 8); + }); + + onFocus(): void { + this.open.set(true); + } + + onBlur(): void { + // Deferred so a click on a result (which fires before blur settles) still registers. + setTimeout(() => this.open.set(false), 150); + } + + /** RouterLink on the result anchor handles navigation; this just resets the search UI after the click. */ + selectResult(): void { + this.query.set(''); + this.open.set(false); + } +} diff --git a/src/app/features/project-editor/components/save-bar/project-editor-save-bar.component.html b/src/app/features/project-editor/components/save-bar/project-editor-save-bar.component.html index 19d3073..3bd0b00 100644 --- a/src/app/features/project-editor/components/save-bar/project-editor-save-bar.component.html +++ b/src/app/features/project-editor/components/save-bar/project-editor-save-bar.component.html @@ -8,7 +8,9 @@
{{ (status() === 'published' ? 'builder.statusPublished' : 'builder.statusDraft') | translate }} @if (dirty()) { - {{ 'builder.unsavedChanges' | translate }} + } @else if (lastSavedAt()) { {{ 'builder.lastSaved' | translate }}: {{ formatSavedAt(lastSavedAt()!) }} } @@ -20,6 +22,18 @@ }
+ @if (changesOpen() && changeSummary().length > 0) { + + }
{{ 'builder.undo' | translate }} {{ 'builder.redo' | translate }} diff --git a/src/app/features/project-editor/components/save-bar/project-editor-save-bar.component.scss b/src/app/features/project-editor/components/save-bar/project-editor-save-bar.component.scss index 98a3297..5d6ffce 100644 --- a/src/app/features/project-editor/components/save-bar/project-editor-save-bar.component.scss +++ b/src/app/features/project-editor/components/save-bar/project-editor-save-bar.component.scss @@ -16,6 +16,62 @@ margin-left: 0.5rem; } +.project-editor-save-bar-dirty--toggle { + border: none; + background: none; + padding: 0; + font: inherit; + cursor: pointer; + text-decoration: underline; + text-decoration-style: dotted; + + &:focus-visible { + outline: 2px solid var(--primary-color, #2f6e5d); + outline-offset: 2px; + } +} + +.project-editor-save-bar-changes { + position: absolute; + bottom: 100%; + left: 0; + right: 0; + margin: 0; + padding: var(--space-xs, 0.25rem); + list-style: none; + max-height: 220px; + overflow-y: auto; + background: var(--bg-primary, #fff); + border: 1px solid var(--border-color, #ddd); + border-bottom: none; + box-shadow: var(--shadow-md, 0 -4px 12px rgba(0, 0, 0, 0.08)); + + a { + display: flex; + justify-content: space-between; + gap: var(--space-sm, 0.5rem); + padding: var(--space-xs, 0.25rem) var(--space-sm, 0.5rem); + border-radius: var(--radius-sm, 4px); + color: var(--text-primary, #1f322d); + text-decoration: none; + font-size: var(--font-size-sm, 0.8125rem); + + &:hover, + &:focus-visible { + background: var(--bg-secondary, #f4f4f5); + outline: none; + } + } +} + +.project-editor-save-bar-changes__diff { + color: var(--text-secondary, #6b7280); + font-size: var(--font-size-xs, 0.75rem); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + .project-editor-save-bar-issues { margin: 0.25rem 0 0; padding-left: 1.25rem; diff --git a/src/app/features/project-editor/components/save-bar/project-editor-save-bar.component.ts b/src/app/features/project-editor/components/save-bar/project-editor-save-bar.component.ts index edc782b..0b07048 100644 --- a/src/app/features/project-editor/components/save-bar/project-editor-save-bar.component.ts +++ b/src/app/features/project-editor/components/save-bar/project-editor-save-bar.component.ts @@ -1,6 +1,8 @@ import { ChangeDetectionStrategy, Component, inject, signal } from '@angular/core'; +import { RouterLink } from '@angular/router'; import { TranslatePipe } from '../../../../i18n/translate.pipe'; import { TranslateService } from '../../../../i18n/translate.service'; +import { LangRoutePipe } from '../../../../pipes/lang-route.pipe'; import { ProjectEditorFacade } from '../../facade/project-editor.facade'; import { ButtonComponent } from '../../../../shared/ui/button/button.component'; import { ConfirmDialogComponent } from '../../../../shared/ui/confirm-dialog/confirm-dialog.component'; @@ -8,7 +10,7 @@ import { ConfirmDialogComponent } from '../../../../shared/ui/confirm-dialog/con @Component({ selector: 'app-project-editor-save-bar', standalone: true, - imports: [TranslatePipe, ButtonComponent, ConfirmDialogComponent], + imports: [TranslatePipe, ButtonComponent, ConfirmDialogComponent, RouterLink, LangRoutePipe], templateUrl: './project-editor-save-bar.component.html', styleUrls: ['./project-editor-save-bar.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush @@ -22,6 +24,12 @@ export class ProjectEditorSaveBarComponent { readonly hasBlockingIssues = this.facade.hasBlockingIssues; readonly canUndo = this.facade.canUndo; readonly canRedo = this.facade.canRedo; + readonly changeSummary = this.facade.changeSummary; + readonly changesOpen = signal(false); + + toggleChanges(): void { + this.changesOpen.update(open => !open); + } undo(): void { this.facade.undo(); diff --git a/src/app/features/project-editor/facade/project-editor.facade.ts b/src/app/features/project-editor/facade/project-editor.facade.ts index 489376d..8b0e947 100644 --- a/src/app/features/project-editor/facade/project-editor.facade.ts +++ b/src/app/features/project-editor/facade/project-editor.facade.ts @@ -453,6 +453,34 @@ export class ProjectEditorFacade { }); } + /** + * Reverts a single schema-registered field to its originally loaded/published value. + * Field-level counterpart to resetSection - same "revert to original" semantics, scoped to + * one dot-path instead of a whole bootstrap key. No confirmation needed: it's a single-value + * change that flows through updateBootstrap like any edit, so undo covers it. + */ + resetField(key: string): void { + const original = this.state().originalBootstrap; + if (!original) { + return; + } + const value = JSON.parse(JSON.stringify(this.schema.getByPath(original, key))); + this.updateBootstrap(current => this.setByPath(current, key, value)); + } + + /** Immutable dot-path setter, the write-side counterpart to EditorSchemaService.getByPath. Only used for resetField's scalar/object schema paths - none of the registered field keys index into array elements. */ + private setByPath(source: T, key: string, value: unknown): T { + const [head, ...rest] = key.split('.'); + if (rest.length === 0) { + return { ...source, [head]: value }; + } + const child = (source as Record)[head]; + return { + ...source, + [head]: this.setByPath(typeof child === 'object' && child !== null ? child : {}, rest.join('.'), value), + }; + } + /** Discards the entire draft, reverting to the originally loaded/published bootstrap. Caller is responsible for confirmation UX. */ resetDraft(): void { const original = this.state().originalBootstrap; diff --git a/src/app/features/project-editor/pages/project-editor-page.component.html b/src/app/features/project-editor/pages/project-editor-page.component.html index 63905b5..2ab609a 100644 --- a/src/app/features/project-editor/pages/project-editor-page.component.html +++ b/src/app/features/project-editor/pages/project-editor-page.component.html @@ -13,6 +13,7 @@ {{ 'builder.backToDashboard' | translate }} + diff --git a/src/app/features/project-editor/pages/project-editor-page.component.scss b/src/app/features/project-editor/pages/project-editor-page.component.scss index e4e4ca4..47db6c8 100644 --- a/src/app/features/project-editor/pages/project-editor-page.component.scss +++ b/src/app/features/project-editor/pages/project-editor-page.component.scss @@ -309,6 +309,11 @@ color: var(--text-primary); } +.project-editor-layout__search { + display: block; + margin-bottom: var(--space-md, 1rem); +} + .project-editor-live-preview__hint { margin: 0 0 var(--space-sm, 0.5rem); font-size: var(--font-size-xs, 0.75rem); diff --git a/src/app/features/project-editor/pages/project-editor-page.component.ts b/src/app/features/project-editor/pages/project-editor-page.component.ts index 878d028..58935da 100644 --- a/src/app/features/project-editor/pages/project-editor-page.component.ts +++ b/src/app/features/project-editor/pages/project-editor-page.component.ts @@ -26,6 +26,7 @@ import { ProjectEditorSaveBarComponent } from '../components/save-bar/project-ed import { EDITOR_SECTION_BOOTSTRAP_KEYS, ProjectEditorSectionId } from '../models/project-editor.model'; import { BUILDER_GROUPS, SECTION_TO_GROUP } from '../builder/builder-groups.model'; import { BuilderLivePreviewComponent } from '../components/live-preview/builder-live-preview.component'; +import { BuilderPropertySearchComponent } from '../components/property-search/builder-property-search.component'; /** Sections that currently expose highlight-mapped fields (see HighlightSourceDirective usages) - showing the panel elsewhere would just be an inert, unhighlightable diagram. */ const SECTIONS_WITH_LIVE_PREVIEW: ProjectEditorSectionId[] = ['branding', 'theme', 'homepage']; @@ -73,6 +74,7 @@ const SECTION_LABEL_KEYS: Record = { ButtonComponent, ConfirmDialogComponent, BuilderLivePreviewComponent, + BuilderPropertySearchComponent, ], templateUrl: './project-editor-page.component.html', styleUrls: ['./project-editor-page.component.scss'], diff --git a/src/app/features/project-editor/sections/branding-section.component.html b/src/app/features/project-editor/sections/branding-section.component.html index 9db47b7..bc399ce 100644 --- a/src/app/features/project-editor/sections/branding-section.component.html +++ b/src/app/features/project-editor/sections/branding-section.component.html @@ -9,6 +9,9 @@ [error]="fieldError('branding.logoUrl')" [usedBy]="[('builder.usedByHeader' | translate), ('builder.usedByFooter' | translate)]" [usedByLabel]="'builder.usedByLabel' | translate" + [showReset]="isFieldModified('branding.logoUrl')" + [resetLabel]="'builder.resetField' | translate" + (resetClicked)="resetField('branding.logoUrl')" > @@ -28,6 +31,9 @@ [hint]="'builder.marketplaceTitleDesc' | translate" [usedBy]="[('builder.usedByHeader' | translate), ('builder.usedBySeo' | translate)]" [usedByLabel]="'builder.usedByLabel' | translate" + [showReset]="isFieldModified('seo.default.title')" + [resetLabel]="'builder.resetField' | translate" + (resetClicked)="resetField('seo.default.title')" > diff --git a/src/app/features/project-editor/sections/branding-section.component.ts b/src/app/features/project-editor/sections/branding-section.component.ts index fe88b2b..aa69101 100644 --- a/src/app/features/project-editor/sections/branding-section.component.ts +++ b/src/app/features/project-editor/sections/branding-section.component.ts @@ -29,6 +29,10 @@ export class ProjectEditorBrandingSectionComponent { const messageKey = this.facade.fieldError(key); return messageKey ? this.translate.t(messageKey) : null; }; + readonly isFieldModified = (key: string): boolean => this.facade.modifiedFields().has(key); + resetField(key: string): void { + this.facade.resetField(key); + } updateField(key: K, value: string): void { this.facade.updateBootstrap(current => ({ diff --git a/src/app/features/project-editor/sections/navigation-section.component.html b/src/app/features/project-editor/sections/navigation-section.component.html index 207b0a3..30164ae 100644 --- a/src/app/features/project-editor/sections/navigation-section.component.html +++ b/src/app/features/project-editor/sections/navigation-section.component.html @@ -11,6 +11,9 @@
+ @if (headerLinks().length === 0) { + + } @for (item of headerLinks(); track item.id) {
@@ -45,6 +48,9 @@ @if (footerLinks(); as footer) {
+ @if (footer.length === 0) { + + } @for (item of footer; track item.id) {
diff --git a/src/app/features/project-editor/sections/navigation-section.component.ts b/src/app/features/project-editor/sections/navigation-section.component.ts index f43652e..361d0ef 100644 --- a/src/app/features/project-editor/sections/navigation-section.component.ts +++ b/src/app/features/project-editor/sections/navigation-section.component.ts @@ -10,12 +10,13 @@ import { SectionCardComponent } from '../../../shared/ui/section-card/section-ca import { ToggleComponent } from '../../../shared/ui/toggle/toggle.component'; import { LocaleTabsComponent } from '../../../shared/ui/locale-tabs/locale-tabs.component'; import { SelectComponent, SelectOption } from '../../../shared/ui/select/select.component'; +import { EmptyStateComponent } from '../../../shared/ui/empty-state/empty-state.component'; import { ContentManagementFacade } from '../../content-management/facade/content-management.facade'; @Component({ selector: 'app-project-editor-navigation-section', standalone: true, - imports: [FormsModule, TranslatePipe, ButtonComponent, InputComponent, SectionCardComponent, ToggleComponent, LocaleTabsComponent, SelectComponent], + imports: [FormsModule, TranslatePipe, ButtonComponent, InputComponent, SectionCardComponent, ToggleComponent, LocaleTabsComponent, SelectComponent, EmptyStateComponent], templateUrl: './navigation-section.component.html', styleUrls: ['./section.shared.scss'], changeDetection: ChangeDetectionStrategy.OnPush diff --git a/src/app/features/project-editor/sections/theme-section.component.html b/src/app/features/project-editor/sections/theme-section.component.html index 9428427..114fee5 100644 --- a/src/app/features/project-editor/sections/theme-section.component.html +++ b/src/app/features/project-editor/sections/theme-section.component.html @@ -8,6 +8,9 @@ [error]="fieldError('theme.palette.primary')" [usedBy]="[('builder.usedByButtons' | translate), ('builder.usedByLinks' | translate), ('builder.usedByBadges' | translate)]" [usedByLabel]="'builder.usedByLabel' | translate" + [showReset]="isFieldModified('theme.palette.primary')" + [resetLabel]="'builder.resetField' | translate" + (resetClicked)="resetField('theme.palette.primary')" > diff --git a/src/app/features/project-editor/sections/theme-section.component.ts b/src/app/features/project-editor/sections/theme-section.component.ts index e932399..7942740 100644 --- a/src/app/features/project-editor/sections/theme-section.component.ts +++ b/src/app/features/project-editor/sections/theme-section.component.ts @@ -38,6 +38,10 @@ export class ProjectEditorThemeSectionComponent { const messageKey = this.facade.fieldError(key); return messageKey ? this.translate.t(messageKey) : null; }; + readonly isFieldModified = (key: string): boolean => this.facade.modifiedFields().has(key); + resetField(key: string): void { + this.facade.resetField(key); + } readonly themeModeOptions: ThemeModeOption[] = [ { value: 'light', labelKey: 'builder.themeModeLight', descKey: 'builder.themeModeLightDesc' }, diff --git a/src/app/i18n/en.ts b/src/app/i18n/en.ts index 61ac50f..31557d2 100644 --- a/src/app/i18n/en.ts +++ b/src/app/i18n/en.ts @@ -880,6 +880,16 @@ export const en: Translations = { usedByLinks: 'Links', usedByBadges: 'Badges', usedByPageBackground: 'Page background', + resetField: 'Reset', + propertySearchLabel: 'Search settings', + propertySearchPlaceholder: 'Search settings…', + propertySearchResultsLabel: 'Search results', + propertySearchNoResults: 'No matching settings.', + unsavedChangesPanelTitle: 'Unsaved changes', + navigationHeaderEmptyTitle: 'No header links yet', + navigationHeaderEmptyDesc: 'Add a link so customers can navigate from the header.', + navigationFooterEmptyTitle: 'No footer links yet', + navigationFooterEmptyDesc: 'Add a link so customers can navigate from the footer.', addBlockHint: 'Add a block to your homepage:', dragToReorder: 'Drag to reorder', blockRemoveConfirm: 'Remove this block from the homepage?', diff --git a/src/app/i18n/hy.ts b/src/app/i18n/hy.ts index 5775e5e..4ae271e 100644 --- a/src/app/i18n/hy.ts +++ b/src/app/i18n/hy.ts @@ -880,6 +880,16 @@ export const hy: Translations = { usedByLinks: 'Հղումներ', usedByBadges: 'Կրծքանշաններ', usedByPageBackground: 'Էջի ֆոն', + resetField: 'Վերականգնել', + propertySearchLabel: 'Որոնել կարգավորումներ', + propertySearchPlaceholder: 'Որոնել կարգավորումներ…', + propertySearchResultsLabel: 'Որոնման արդյունքներ', + propertySearchNoResults: 'Համընկնումներ չեն գտնվել։', + unsavedChangesPanelTitle: 'Չպահպանված փոփոխություններ', + navigationHeaderEmptyTitle: 'Վերնագրում հղումներ դեռ չկան', + navigationHeaderEmptyDesc: 'Ավելացրեք հղում, որպեսզի գնորդները կարողանան նավարկել վերնագրից։', + navigationFooterEmptyTitle: 'Ստորագրում հղումներ դեռ չկան', + navigationFooterEmptyDesc: 'Ավելացրեք հղում, որպեսզի գնորդները կարողանան նավարկել ստորագրից։', addBlockHint: 'Ավելացնել բլոկ գլխավոր էջում.', dragToReorder: 'Քաշեք՝ կարգը փոխելու համար', blockRemoveConfirm: 'Հեռացնե՞լ այս բլոկը գլխավոր էջից։', diff --git a/src/app/i18n/ru.ts b/src/app/i18n/ru.ts index 29fbb8c..c75771c 100644 --- a/src/app/i18n/ru.ts +++ b/src/app/i18n/ru.ts @@ -880,6 +880,16 @@ export const ru: Translations = { usedByLinks: 'Ссылки', usedByBadges: 'Значки', usedByPageBackground: 'Фон страницы', + resetField: 'Сбросить', + propertySearchLabel: 'Поиск настроек', + propertySearchPlaceholder: 'Поиск настроек…', + propertySearchResultsLabel: 'Результаты поиска', + propertySearchNoResults: 'Совпадений не найдено.', + unsavedChangesPanelTitle: 'Несохранённые изменения', + navigationHeaderEmptyTitle: 'В шапке пока нет ссылок', + navigationHeaderEmptyDesc: 'Добавьте ссылку, чтобы покупатели могли перемещаться из шапки сайта.', + navigationFooterEmptyTitle: 'В подвале пока нет ссылок', + navigationFooterEmptyDesc: 'Добавьте ссылку, чтобы покупатели могли перемещаться из подвала сайта.', addBlockHint: 'Добавить блок на главную страницу:', dragToReorder: 'Перетащите, чтобы изменить порядок', blockRemoveConfirm: 'Удалить этот блок с главной страницы?', diff --git a/src/app/i18n/translations.ts b/src/app/i18n/translations.ts index 44b4fd3..2ff5508 100644 --- a/src/app/i18n/translations.ts +++ b/src/app/i18n/translations.ts @@ -881,6 +881,16 @@ export interface Translations { usedByLinks: string; usedByBadges: string; usedByPageBackground: string; + resetField: string; + propertySearchLabel: string; + propertySearchPlaceholder: string; + propertySearchResultsLabel: string; + propertySearchNoResults: string; + unsavedChangesPanelTitle: string; + navigationHeaderEmptyTitle: string; + navigationHeaderEmptyDesc: string; + navigationFooterEmptyTitle: string; + navigationFooterEmptyDesc: string; blockRemoveConfirm: string; blockHero: string; blockHeroDesc: string; diff --git a/src/app/shared/ui/form-field/form-field.component.html b/src/app/shared/ui/form-field/form-field.component.html index bd8aa46..eae8a6b 100644 --- a/src/app/shared/ui/form-field/form-field.component.html +++ b/src/app/shared/ui/form-field/form-field.component.html @@ -1,11 +1,19 @@
@if (label()) { -