From 3937483469e69a8f4f446ec0044930fa451427fa Mon Sep 17 00:00:00 2001 From: Andrii Kostenko Date: Fri, 14 Nov 2025 11:19:39 +0000 Subject: [PATCH 1/2] markdown widget --- .../db-table-widgets.component.ts | 3 ++ .../markdown/markdown.component.css | 6 +++ .../markdown/markdown.component.html | 11 +++++ .../markdown/markdown.component.spec.ts | 30 ++++++++++++ .../markdown/markdown.component.ts | 42 ++++++++++++++++ .../markdown/markdown.component.css | 48 +++++++++++++++++++ .../markdown/markdown.component.html | 3 ++ .../markdown/markdown.component.spec.ts | 30 ++++++++++++ .../markdown/markdown.component.ts | 19 ++++++++ .../markdown/markdown.component.css | 40 ++++++++++++++++ .../markdown/markdown.component.html | 13 +++++ .../markdown/markdown.component.spec.ts | 30 ++++++++++++ .../markdown/markdown.component.ts | 29 +++++++++++ frontend/src/app/consts/record-edit-types.ts | 2 + frontend/src/app/consts/record-view-types.ts | 2 + .../src/app/consts/table-display-types.ts | 2 + 16 files changed, 310 insertions(+) create mode 100644 frontend/src/app/components/ui-components/record-edit-fields/markdown/markdown.component.css create mode 100644 frontend/src/app/components/ui-components/record-edit-fields/markdown/markdown.component.html create mode 100644 frontend/src/app/components/ui-components/record-edit-fields/markdown/markdown.component.spec.ts create mode 100644 frontend/src/app/components/ui-components/record-edit-fields/markdown/markdown.component.ts create mode 100644 frontend/src/app/components/ui-components/record-view-fields/markdown/markdown.component.css create mode 100644 frontend/src/app/components/ui-components/record-view-fields/markdown/markdown.component.html create mode 100644 frontend/src/app/components/ui-components/record-view-fields/markdown/markdown.component.spec.ts create mode 100644 frontend/src/app/components/ui-components/record-view-fields/markdown/markdown.component.ts create mode 100644 frontend/src/app/components/ui-components/table-display-fields/markdown/markdown.component.css create mode 100644 frontend/src/app/components/ui-components/table-display-fields/markdown/markdown.component.html create mode 100644 frontend/src/app/components/ui-components/table-display-fields/markdown/markdown.component.spec.ts create mode 100644 frontend/src/app/components/ui-components/table-display-fields/markdown/markdown.component.ts diff --git a/frontend/src/app/components/dashboard/db-table-view/db-table-widgets/db-table-widgets.component.ts b/frontend/src/app/components/dashboard/db-table-view/db-table-widgets/db-table-widgets.component.ts index 46921fd95..1138124d0 100644 --- a/frontend/src/app/components/dashboard/db-table-view/db-table-widgets/db-table-widgets.component.ts +++ b/frontend/src/app/components/dashboard/db-table-view/db-table-widgets/db-table-widgets.component.ts @@ -12,6 +12,7 @@ import { FormsModule } from '@angular/forms'; import { ImageEditComponent } from '../../../ui-components/record-edit-fields/image/image.component'; import { Location } from '@angular/common'; import { LongTextEditComponent } from '../../../ui-components/record-edit-fields/long-text/long-text.component'; +import { MarkdownEditComponent } from '../../../ui-components/record-edit-fields/markdown/markdown.component'; import { MatButtonModule } from '@angular/material/button'; import { MatDialog } from '@angular/material/dialog'; import { MatDialogModule } from '@angular/material/dialog'; @@ -53,6 +54,7 @@ import { normalizeTableName } from 'src/app/lib/normalize'; PasswordEditComponent, ImageEditComponent, CodeEditComponent, + MarkdownEditComponent, WidgetComponent, TextEditComponent, LongTextEditComponent, @@ -160,6 +162,7 @@ export class DbTableWidgetsComponent implements OnInit { } `, JSON: `// No settings required`, + Markdown: `// No settings required`, Money: `// Configure money widget settings // example: { diff --git a/frontend/src/app/components/ui-components/record-edit-fields/markdown/markdown.component.css b/frontend/src/app/components/ui-components/record-edit-fields/markdown/markdown.component.css new file mode 100644 index 000000000..d5062f6f0 --- /dev/null +++ b/frontend/src/app/components/ui-components/record-edit-fields/markdown/markdown.component.css @@ -0,0 +1,6 @@ +.code-editor-box { + min-height: 200px; + border: 1px solid #ccc; + border-radius: 4px; + overflow: hidden; +} diff --git a/frontend/src/app/components/ui-components/record-edit-fields/markdown/markdown.component.html b/frontend/src/app/components/ui-components/record-edit-fields/markdown/markdown.component.html new file mode 100644 index 000000000..f410deb90 --- /dev/null +++ b/frontend/src/app/components/ui-components/record-edit-fields/markdown/markdown.component.html @@ -0,0 +1,11 @@ +{{ normalizedLabel }} {{ required ? '*' : '' }} + +
+ + +
diff --git a/frontend/src/app/components/ui-components/record-edit-fields/markdown/markdown.component.spec.ts b/frontend/src/app/components/ui-components/record-edit-fields/markdown/markdown.component.spec.ts new file mode 100644 index 000000000..ec4fcf891 --- /dev/null +++ b/frontend/src/app/components/ui-components/record-edit-fields/markdown/markdown.component.spec.ts @@ -0,0 +1,30 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { MarkdownEditComponent } from './markdown.component'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { provideHttpClient } from '@angular/common/http'; + +describe('MarkdownEditComponent', () => { + let component: MarkdownEditComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [MarkdownEditComponent, BrowserAnimationsModule], + providers: [provideHttpClient()] + }).compileComponents(); + + fixture = TestBed.createComponent(MarkdownEditComponent); + component = fixture.componentInstance; + + component.widgetStructure = { + widget_params: {} + } as any; + + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/frontend/src/app/components/ui-components/record-edit-fields/markdown/markdown.component.ts b/frontend/src/app/components/ui-components/record-edit-fields/markdown/markdown.component.ts new file mode 100644 index 000000000..89b5eb96a --- /dev/null +++ b/frontend/src/app/components/ui-components/record-edit-fields/markdown/markdown.component.ts @@ -0,0 +1,42 @@ +import { Component, Input } from '@angular/core'; + +import { BaseEditFieldComponent } from '../base-row-field/base-row-field.component'; +import { CodeEditorModule } from '@ngstack/code-editor'; +import { CommonModule } from '@angular/common'; +import { UiSettingsService } from 'src/app/services/ui-settings.service'; + +@Component({ + selector: 'app-edit-markdown', + templateUrl: './markdown.component.html', + styleUrl: './markdown.component.css', + imports: [CommonModule, CodeEditorModule], +}) +export class MarkdownEditComponent extends BaseEditFieldComponent { + @Input() value; + + public mutableCodeModel: Object; + public codeEditorOptions = { + minimap: { enabled: false }, + automaticLayout: true, + scrollBeyondLastLine: false, + wordWrap: 'on', + }; + public codeEditorTheme = 'vs-dark'; + + constructor( + private _uiSettings: UiSettingsService, + ) { + super(); + } + + ngOnInit(): void { + super.ngOnInit(); + this.mutableCodeModel = { + language: 'markdown', + uri: `${this.label}.md`, + value: this.value + } + + this.codeEditorTheme = this._uiSettings.editorTheme; + } +} diff --git a/frontend/src/app/components/ui-components/record-view-fields/markdown/markdown.component.css b/frontend/src/app/components/ui-components/record-view-fields/markdown/markdown.component.css new file mode 100644 index 000000000..27736edaf --- /dev/null +++ b/frontend/src/app/components/ui-components/record-view-fields/markdown/markdown.component.css @@ -0,0 +1,48 @@ +.markdown-container { + padding: 16px; + background: #f9f9f9; + border-radius: 4px; + border: 1px solid #e0e0e0; + min-height: 100px; +} + +.markdown-container ::ng-deep h1, +.markdown-container ::ng-deep h2, +.markdown-container ::ng-deep h3, +.markdown-container ::ng-deep h4, +.markdown-container ::ng-deep h5, +.markdown-container ::ng-deep h6 { + margin-top: 16px; + margin-bottom: 8px; +} + +.markdown-container ::ng-deep p { + margin-bottom: 8px; +} + +.markdown-container ::ng-deep ul, +.markdown-container ::ng-deep ol { + margin-left: 20px; + margin-bottom: 8px; +} + +.markdown-container ::ng-deep code { + background: #f5f5f5; + padding: 2px 4px; + border-radius: 3px; + font-family: monospace; +} + +.markdown-container ::ng-deep pre { + background: #f5f5f5; + padding: 12px; + border-radius: 4px; + overflow-x: auto; +} + +.markdown-container ::ng-deep blockquote { + border-left: 4px solid #ddd; + padding-left: 16px; + margin-left: 0; + color: #666; +} diff --git a/frontend/src/app/components/ui-components/record-view-fields/markdown/markdown.component.html b/frontend/src/app/components/ui-components/record-view-fields/markdown/markdown.component.html new file mode 100644 index 000000000..4270d38fd --- /dev/null +++ b/frontend/src/app/components/ui-components/record-view-fields/markdown/markdown.component.html @@ -0,0 +1,3 @@ +
+ +
diff --git a/frontend/src/app/components/ui-components/record-view-fields/markdown/markdown.component.spec.ts b/frontend/src/app/components/ui-components/record-view-fields/markdown/markdown.component.spec.ts new file mode 100644 index 000000000..a4262aaca --- /dev/null +++ b/frontend/src/app/components/ui-components/record-view-fields/markdown/markdown.component.spec.ts @@ -0,0 +1,30 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { MarkdownRecordViewComponent } from './markdown.component'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { provideMarkdown } from 'ngx-markdown'; +import { provideHttpClient } from '@angular/common/http'; + +describe('MarkdownRecordViewComponent', () => { + let component: MarkdownRecordViewComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [MarkdownRecordViewComponent, BrowserAnimationsModule], + providers: [ + provideHttpClient(), + provideMarkdown() + ] + }).compileComponents(); + + fixture = TestBed.createComponent(MarkdownRecordViewComponent); + component = fixture.componentInstance; + component.value = '# Test Markdown'; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/frontend/src/app/components/ui-components/record-view-fields/markdown/markdown.component.ts b/frontend/src/app/components/ui-components/record-view-fields/markdown/markdown.component.ts new file mode 100644 index 000000000..9972dae88 --- /dev/null +++ b/frontend/src/app/components/ui-components/record-view-fields/markdown/markdown.component.ts @@ -0,0 +1,19 @@ +import { BaseRecordViewFieldComponent } from '../base-record-view-field/base-record-view-field.component'; +import { Component, Injectable, OnInit } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { MarkdownModule } from 'ngx-markdown'; + +@Injectable() +@Component({ + selector: 'app-markdown-record-view', + templateUrl: './markdown.component.html', + styleUrls: ['../base-record-view-field/base-record-view-field.component.css', './markdown.component.css'], + imports: [CommonModule, MarkdownModule] +}) +export class MarkdownRecordViewComponent extends BaseRecordViewFieldComponent implements OnInit { + public renderedMarkdown: string = ''; + + ngOnInit(): void { + this.renderedMarkdown = this.value || ''; + } +} diff --git a/frontend/src/app/components/ui-components/table-display-fields/markdown/markdown.component.css b/frontend/src/app/components/ui-components/table-display-fields/markdown/markdown.component.css new file mode 100644 index 000000000..6c6f41777 --- /dev/null +++ b/frontend/src/app/components/ui-components/table-display-fields/markdown/markdown.component.css @@ -0,0 +1,40 @@ +.markdown-preview { + display: inline-block; + max-width: 300px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.markdown-preview ::ng-deep p { + margin: 0; + display: inline; +} + +.markdown-preview ::ng-deep h1, +.markdown-preview ::ng-deep h2, +.markdown-preview ::ng-deep h3, +.markdown-preview ::ng-deep h4, +.markdown-preview ::ng-deep h5, +.markdown-preview ::ng-deep h6 { + margin: 0; + display: inline; + font-size: inherit; + font-weight: bold; +} + +.markdown-preview ::ng-deep ul, +.markdown-preview ::ng-deep ol { + display: inline; + list-style: none; + margin: 0; + padding: 0; +} + +.markdown-preview ::ng-deep code { + background: #f5f5f5; + padding: 1px 3px; + border-radius: 2px; + font-family: monospace; + font-size: 0.9em; +} diff --git a/frontend/src/app/components/ui-components/table-display-fields/markdown/markdown.component.html b/frontend/src/app/components/ui-components/table-display-fields/markdown/markdown.component.html new file mode 100644 index 000000000..35bd74e87 --- /dev/null +++ b/frontend/src/app/components/ui-components/table-display-fields/markdown/markdown.component.html @@ -0,0 +1,13 @@ +
+ + + + +
diff --git a/frontend/src/app/components/ui-components/table-display-fields/markdown/markdown.component.spec.ts b/frontend/src/app/components/ui-components/table-display-fields/markdown/markdown.component.spec.ts new file mode 100644 index 000000000..f18c93c91 --- /dev/null +++ b/frontend/src/app/components/ui-components/table-display-fields/markdown/markdown.component.spec.ts @@ -0,0 +1,30 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { MarkdownDisplayComponent } from './markdown.component'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { provideMarkdown } from 'ngx-markdown'; +import { provideHttpClient } from '@angular/common/http'; + +describe('MarkdownDisplayComponent', () => { + let component: MarkdownDisplayComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [MarkdownDisplayComponent, BrowserAnimationsModule], + providers: [ + provideHttpClient(), + provideMarkdown() + ] + }).compileComponents(); + + fixture = TestBed.createComponent(MarkdownDisplayComponent); + component = fixture.componentInstance; + component.value = '# Test Markdown'; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/frontend/src/app/components/ui-components/table-display-fields/markdown/markdown.component.ts b/frontend/src/app/components/ui-components/table-display-fields/markdown/markdown.component.ts new file mode 100644 index 000000000..55cf7d0c9 --- /dev/null +++ b/frontend/src/app/components/ui-components/table-display-fields/markdown/markdown.component.ts @@ -0,0 +1,29 @@ +import { BaseTableDisplayFieldComponent } from '../base-table-display-field/base-table-display-field.component'; +import { ClipboardModule } from '@angular/cdk/clipboard'; +import { Component, OnInit } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { MarkdownModule } from 'ngx-markdown'; +import { MatButtonModule } from '@angular/material/button'; +import { MatIconModule } from '@angular/material/icon'; +import { MatTooltipModule } from '@angular/material/tooltip'; + +@Component({ + selector: 'app-markdown-display', + templateUrl: './markdown.component.html', + styleUrls: ['../base-table-display-field/base-table-display-field.component.css', './markdown.component.css'], + imports: [CommonModule, MarkdownModule, ClipboardModule, MatIconModule, MatButtonModule, MatTooltipModule] +}) +export class MarkdownDisplayComponent extends BaseTableDisplayFieldComponent implements OnInit { + public renderedMarkdown: string = ''; + public truncatedValue: string = ''; + + ngOnInit(): void { + this.renderedMarkdown = this.value || ''; + // Truncate long markdown for table display + if (this.value && this.value.length > 100) { + this.truncatedValue = this.value.substring(0, 100) + '...'; + } else { + this.truncatedValue = this.value || '—'; + } + } +} diff --git a/frontend/src/app/consts/record-edit-types.ts b/frontend/src/app/consts/record-edit-types.ts index 7f01a9548..ba6fabe41 100644 --- a/frontend/src/app/consts/record-edit-types.ts +++ b/frontend/src/app/consts/record-edit-types.ts @@ -11,6 +11,7 @@ import { IdEditComponent } from '../components/ui-components/record-edit-fields/ import { ImageEditComponent } from '../components/ui-components/record-edit-fields/image/image.component'; import { JsonEditorEditComponent } from '../components/ui-components/record-edit-fields/json-editor/json-editor.component'; import { LongTextEditComponent } from 'src/app/components/ui-components/record-edit-fields/long-text/long-text.component' +import { MarkdownEditComponent } from '../components/ui-components/record-edit-fields/markdown/markdown.component'; import { MoneyEditComponent } from '../components/ui-components/record-edit-fields/money/money.component'; import { NumberEditComponent } from 'src/app/components/ui-components/record-edit-fields/number/number.component'; import { PasswordEditComponent } from '../components/ui-components/record-edit-fields/password/password.component'; @@ -44,6 +45,7 @@ export const UIwidgets = { Foreign_key: ForeignKeyEditComponent, Image: ImageEditComponent, JSON: JsonEditorEditComponent, + Markdown: MarkdownEditComponent, Money: MoneyEditComponent, Number: NumberEditComponent, Password: PasswordEditComponent, diff --git a/frontend/src/app/consts/record-view-types.ts b/frontend/src/app/consts/record-view-types.ts index eb5d416a8..13553a5b9 100644 --- a/frontend/src/app/consts/record-view-types.ts +++ b/frontend/src/app/consts/record-view-types.ts @@ -10,6 +10,7 @@ import { IdRecordViewComponent } from '../components/ui-components/record-view-f import { ImageRecordViewComponent } from '../components/ui-components/record-view-fields/image/image.component'; import { JsonEditorRecordViewComponent } from '../components/ui-components/record-view-fields/json-editor/json-editor.component'; import { LongTextRecordViewComponent } from 'src/app/components/ui-components/record-view-fields/long-text/long-text.component'; +import { MarkdownRecordViewComponent } from '../components/ui-components/record-view-fields/markdown/markdown.component'; import { MoneyRecordViewComponent } from '../components/ui-components/record-view-fields/money/money.component'; import { NumberRecordViewComponent } from '../components/ui-components/record-view-fields/number/number.component'; import { PasswordRecordViewComponent } from '../components/ui-components/record-view-fields/password/password.component'; @@ -32,6 +33,7 @@ export const UIwidgets = { Time: TimeRecordViewComponent, DateTime: DateTimeRecordViewComponent, JSON: JsonEditorRecordViewComponent, + Markdown: MarkdownRecordViewComponent, Textarea: LongTextRecordViewComponent, String: TextRecordViewComponent, Readonly: StaticTextRecordViewComponent, diff --git a/frontend/src/app/consts/table-display-types.ts b/frontend/src/app/consts/table-display-types.ts index d0eda963a..e640d660a 100644 --- a/frontend/src/app/consts/table-display-types.ts +++ b/frontend/src/app/consts/table-display-types.ts @@ -10,6 +10,7 @@ import { IdDisplayComponent } from '../components/ui-components/table-display-fi import { ImageDisplayComponent } from '../components/ui-components/table-display-fields/image/image.component'; import { JsonEditorDisplayComponent } from '../components/ui-components/table-display-fields/json-editor/json-editor.component'; import { LongTextDisplayComponent } from 'src/app/components/ui-components/table-display-fields/long-text/long-text.component'; +import { MarkdownDisplayComponent } from '../components/ui-components/table-display-fields/markdown/markdown.component'; import { MoneyDisplayComponent } from '../components/ui-components/table-display-fields/money/money.component'; import { NumberDisplayComponent } from '../components/ui-components/table-display-fields/number/number.component'; import { PasswordDisplayComponent } from '../components/ui-components/table-display-fields/password/password.component'; @@ -37,6 +38,7 @@ export const UIwidgets = { Foreign_key: ForeignKeyDisplayComponent, Image: ImageDisplayComponent, JSON: JsonEditorDisplayComponent, + Markdown: MarkdownDisplayComponent, Money: MoneyDisplayComponent, Number: NumberDisplayComponent, Password: PasswordDisplayComponent, From 4c38c9d35b57e93f08b334e4f0bd550c309ce844 Mon Sep 17 00:00:00 2001 From: Andrii Kostenko Date: Fri, 14 Nov 2025 12:21:10 +0000 Subject: [PATCH 2/2] markdown widget --- .../db-table-ai-panel.component.css | 16 ++++- .../markdown/markdown.component.html | 2 +- .../markdown/markdown.component.spec.ts | 69 +++++++++++++++++++ .../markdown/markdown.component.ts | 63 +++++++++++++++-- 4 files changed, 142 insertions(+), 8 deletions(-) diff --git a/frontend/src/app/components/dashboard/db-table-view/db-table-ai-panel/db-table-ai-panel.component.css b/frontend/src/app/components/dashboard/db-table-view/db-table-ai-panel/db-table-ai-panel.component.css index 6665fa7c2..6f01a799b 100644 --- a/frontend/src/app/components/dashboard/db-table-view/db-table-ai-panel/db-table-ai-panel.component.css +++ b/frontend/src/app/components/dashboard/db-table-view/db-table-ai-panel/db-table-ai-panel.component.css @@ -189,7 +189,21 @@ .ai-message ::ng-deep ol, .ai-message ::ng-deep ul { - padding-left: 28px; + padding-left: 20px; + margin: 8px 0; +} + +.ai-message ::ng-deep li { + margin: 4px 0; + line-height: 1.5; +} + +.ai-message ::ng-deep ol { + list-style-position: outside; +} + +.ai-message ::ng-deep ul { + list-style-position: outside; } .ai-message ::ng-deep pre { diff --git a/frontend/src/app/components/ui-components/table-display-fields/markdown/markdown.component.html b/frontend/src/app/components/ui-components/table-display-fields/markdown/markdown.component.html index 35bd74e87..aaa8bbe84 100644 --- a/frontend/src/app/components/ui-components/table-display-fields/markdown/markdown.component.html +++ b/frontend/src/app/components/ui-components/table-display-fields/markdown/markdown.component.html @@ -1,6 +1,6 @@
- + {{ displayTitle }}