Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -53,6 +54,7 @@ import { normalizeTableName } from 'src/app/lib/normalize';
PasswordEditComponent,
ImageEditComponent,
CodeEditComponent,
MarkdownEditComponent,
WidgetComponent,
TextEditComponent,
LongTextEditComponent,
Expand Down Expand Up @@ -160,6 +162,7 @@ export class DbTableWidgetsComponent implements OnInit {
}
`,
JSON: `// No settings required`,
Markdown: `// No settings required`,
Money: `// Configure money widget settings
// example:
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.code-editor-box {
min-height: 200px;
border: 1px solid #ccc;
border-radius: 4px;
overflow: hidden;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<span class="mat-body-1">{{ normalizedLabel }} {{ required ? '*' : '' }}</span>

<div class="code-editor-box" data-hj-suppress>
<ngs-code-editor
[theme]="codeEditorTheme"
[codeModel]="mutableCodeModel"
[options]="codeEditorOptions"
[readOnly]="readonly"
(valueChanged)="onFieldChange.emit($event)">
</ngs-code-editor>
</div>
Original file line number Diff line number Diff line change
@@ -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<MarkdownEditComponent>;

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();
});
});
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<div class="markdown-container">
<markdown [data]="renderedMarkdown"></markdown>
</div>
Original file line number Diff line number Diff line change
@@ -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<MarkdownRecordViewComponent>;

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();
});
});
Original file line number Diff line number Diff line change
@@ -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 || '';
}
Comment on lines +14 to +18

Copilot AI Nov 14, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The renderedMarkdown property is unnecessary as it's just a copy of this.value. Consider using value directly in the template: <markdown [data]="value || ''"></markdown>. This simplifies the code and removes unnecessary state management.

Suggested change
public renderedMarkdown: string = '';
ngOnInit(): void {
this.renderedMarkdown = this.value || '';
}

Copilot uses AI. Check for mistakes.
}
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<div class="field-display">
<span class="field-value markdown-preview" [matTooltip]="value">
{{ displayTitle }}
</span>
<button type="button" mat-icon-button
class="field-copy-button"
matTooltip="Copy"
[cdkCopyToClipboard]="value"
(cdkCopyToClipboardCopied)="onCopyToClipboard.emit('Field value was copied to clipboard.')"
(click)="$event.stopPropagation()">
<mat-icon>content_copy</mat-icon>
</button>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
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<MarkdownDisplayComponent>;

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();
});

describe('Title extraction', () => {
it('should extract H1 heading as title', () => {
component.value = '# Main Title\n\nSome paragraph text here.';
component.ngOnInit();
expect(component.displayTitle).toBe('Main Title');
});

it('should extract H2 heading as title', () => {
component.value = '## Secondary Title\n\nContent follows.';
component.ngOnInit();
expect(component.displayTitle).toBe('Secondary Title');
});

it('should prioritize heading over paragraph', () => {
component.value = 'Some intro text\n\n# Actual Title\n\nParagraph content.';
component.ngOnInit();
expect(component.displayTitle).toBe('Actual Title');
});

it('should use first paragraph if no heading exists', () => {
component.value = 'This is the first paragraph.\n\nThis is the second paragraph.';
component.ngOnInit();
expect(component.displayTitle).toBe('This is the first paragraph.');
});

it('should truncate long titles at 100 characters', () => {
const longTitle = 'a'.repeat(150);
component.value = `# ${longTitle}`;
component.ngOnInit();
expect(component.displayTitle).toBe('a'.repeat(100) + '...');
});

it('should handle markdown with bold and italic formatting', () => {
component.value = '# **Bold** and *italic* title';
component.ngOnInit();
expect(component.displayTitle).toBe('**Bold** and *italic* title');
});

it('should handle empty markdown', () => {
component.value = '';
component.ngOnInit();
expect(component.displayTitle).toBe('—');
});

it('should handle null/undefined markdown', () => {
component.value = null as any;
component.ngOnInit();
expect(component.displayTitle).toBe('—');
});

it('should handle markdown with only whitespace', () => {
component.value = ' \n\n ';
component.ngOnInit();
expect(component.displayTitle).toBe('—');
});

it('should extract title from markdown with code blocks', () => {
component.value = '# API Documentation\n\n```javascript\nconst x = 5;\n```';
component.ngOnInit();
expect(component.displayTitle).toBe('API Documentation');
});

it('should extract title from markdown with lists', () => {
component.value = '# Shopping List\n\n- Item 1\n- Item 2';
component.ngOnInit();
expect(component.displayTitle).toBe('Shopping List');
});
});
});
Loading
Loading