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
new file mode 100644
index 0000000..57e8f4d
--- /dev/null
+++ b/src/app/features/project-editor/components/html-editor/marketplace-html-editor.component.html
@@ -0,0 +1,24 @@
+
+
+ @for (item of toolbar; track item.id) {
+
+ }
+
+
+
+ @if (showCode()) {
+
+ } @else {
+
+ }
+
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
new file mode 100644
index 0000000..5696ff0
--- /dev/null
+++ b/src/app/features/project-editor/components/html-editor/marketplace-html-editor.component.scss
@@ -0,0 +1,27 @@
+.html-editor {
+ display: flex;
+ flex-direction: column;
+ gap: 0.5rem;
+}
+
+.html-editor-toolbar {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.25rem;
+}
+
+.html-editor-surface {
+ min-height: 160px;
+ border: 1px solid var(--border, #ccc);
+ border-radius: 4px;
+ padding: 0.5rem;
+ overflow-y: auto;
+}
+
+.html-editor-code {
+ min-height: 160px;
+ font-family: monospace;
+ border: 1px solid var(--border, #ccc);
+ border-radius: 4px;
+ padding: 0.5rem;
+}
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
new file mode 100644
index 0000000..c0df2f2
--- /dev/null
+++ b/src/app/features/project-editor/components/html-editor/marketplace-html-editor.component.ts
@@ -0,0 +1,88 @@
+import { ChangeDetectionStrategy, Component, ElementRef, EventEmitter, Input, OnChanges, Output, SimpleChanges, ViewChild, signal } from '@angular/core';
+
+export interface HtmlEditorToolbarCommand {
+ id: string;
+ label: string;
+ command: string;
+ value?: 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: '' },
+];
+
+@Component({
+ selector: 'app-marketplace-html-editor',
+ standalone: true,
+ imports: [],
+ templateUrl: './marketplace-html-editor.component.html',
+ styleUrls: ['./marketplace-html-editor.component.scss'],
+ changeDetection: ChangeDetectionStrategy.OnPush
+})
+export class MarketplaceHtmlEditorComponent implements OnChanges {
+ @Input() html = '';
+ @Output() htmlChange = new EventEmitter();
+ @ViewChild('surface', { static: true }) surface!: ElementRef;
+
+ readonly toolbar = HTML_EDITOR_TOOLBAR;
+ readonly showCode = signal(false);
+ readonly codeValue = signal('');
+
+ ngOnChanges(changes: SimpleChanges): void {
+ if (changes['html'] && this.surface && this.surface.nativeElement.innerHTML !== (this.html || '')) {
+ this.surface.nativeElement.innerHTML = this.html || '';
+ }
+ }
+
+ runCommand(item: HtmlEditorToolbarCommand): void {
+ this.surface.nativeElement.focus();
+ if (item.command === 'createLink') {
+ const url = window.prompt('URL');
+ if (!url) {
+ return;
+ }
+ document.execCommand('createLink', false, url);
+ } else if (item.command === 'insertImage') {
+ const url = window.prompt('Image URL');
+ if (!url) {
+ return;
+ }
+ document.execCommand('insertImage', false, url);
+ } else {
+ document.execCommand(item.command, false, item.value);
+ }
+ this.emitChange();
+ }
+
+ onInput(): void {
+ this.emitChange();
+ }
+
+ toggleCode(): void {
+ if (!this.showCode()) {
+ this.codeValue.set(this.surface.nativeElement.innerHTML);
+ this.showCode.set(true);
+ return;
+ }
+ this.surface.nativeElement.innerHTML = this.codeValue();
+ this.showCode.set(false);
+ this.emitChange();
+ }
+
+ updateCode(value: string): void {
+ this.codeValue.set(value);
+ }
+
+ private emitChange(): void {
+ this.htmlChange.emit(this.surface.nativeElement.innerHTML);
+ }
+}