@c8y/ngx-components
Version:
Angular modules for Cumulocity IoT applications
370 lines (363 loc) • 26.9 kB
JavaScript
import * as i0 from '@angular/core';
import { Injectable, inject, ViewChild, Input, Component } from '@angular/core';
import { gettext } from '@c8y/ngx-components/gettext';
import * as i1 from '@c8y/ngx-components';
import { C8yValidators, FormGroupComponent, DropAreaComponent, EmptyStateComponent, IconDirective, C8yTranslatePipe, MarkdownToHtmlPipe } from '@c8y/ngx-components';
import * as i1$1 from '@angular/forms';
import { Validators, ReactiveFormsModule, FormsModule } from '@angular/forms';
import * as i2 from '@c8y/client';
import * as i3 from '@ngx-translate/core';
import { WidgetConfigService } from '@c8y/ngx-components/context-dashboard';
import { AsyncPipe } from '@angular/common';
import { BehaviorSubject, Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { EditorComponent } from '@c8y/ngx-components/editor';
const MarkdownSourceType = {
WRITE: 'writeMarkdown',
UPLOAD: 'uploadBinary',
URL: 'uploadUrl'
};
class MarkdownWidgetService {
constructor(fileService, inventory, binary, alert, translate, fetchClient, appStateService) {
this.fileService = fileService;
this.inventory = inventory;
this.binary = binary;
this.alert = alert;
this.translate = translate;
this.fetchClient = fetchClient;
this.appStateService = appStateService;
this.headers = { 'Content-Type': 'text/markdown' };
}
/**
* Retrieves a markdown file from the inventory by its binary ID.
* @param markdownBinaryId - The ID of the binary managed object.
* @returns The file if found, otherwise null.
*/
async getFile(markdownBinaryId) {
if (!markdownBinaryId) {
return null;
}
try {
const { data: markdownBinaryMo } = await this.inventory.detail(markdownBinaryId);
const file = await this.fileService.getFile(markdownBinaryMo);
return file;
}
catch (e) {
const text = this.translate.instant(gettext('Unable to retrieve binary with ID: {{ markdownBinaryId }}'), { markdownBinaryId });
this.alert.danger(text, e?.data);
}
return null;
}
/**
* Uploads a markdown file to the inventory as a binary.
* @param file - The file to upload.
* @returns The ID of the created binary managed object.
*/
async uploadFile(file) {
const { data: mo } = await this.binary.create(file);
return mo.id;
}
/**
* Fetches markdown content from a URL.
* For internal URLs (e.g., `/readme.md`), uses FetchClient with the app's context path.
* For external URLs, uses XMLHttpRequest to avoid CORS issues with auth headers.
* @param url - The URL to fetch content from.
* @returns The markdown content as a string, or empty string on error.
*/
async getContentFromUrl(url) {
if (url?.toLowerCase() === '/readme.md') {
return this.getContentFromInternalUrl(url);
}
return this.getContentFromExternalUrl(url);
}
async getContentFromInternalUrl(url) {
try {
const contextPath = this.appStateService.state.app.contextPath;
const options = {
method: 'GET',
headers: this.headers
};
const response = await this.fetchClient.fetch(`/apps/${contextPath}${url}`, options);
if (response.status === 200) {
return response.text();
}
}
catch {
// ignore error, return empty string
}
return '';
}
getContentFromExternalUrl(url) {
return new Promise(resolve => {
const req = new XMLHttpRequest();
req.onreadystatechange = () => {
if (req.readyState === 4) {
if (req.status === 200) {
resolve(req.response);
}
else {
resolve('');
}
}
};
req.onerror = () => resolve('');
req.open('GET', url);
req.responseType = 'text';
req.setRequestHeader('Accept', 'text/html');
req.send();
});
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: MarkdownWidgetService, deps: [{ token: i1.FilesService }, { token: i2.InventoryService }, { token: i2.InventoryBinaryService }, { token: i1.AlertService }, { token: i3.TranslateService }, { token: i2.FetchClient }, { token: i1.AppStateService }], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: MarkdownWidgetService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: MarkdownWidgetService, decorators: [{
type: Injectable,
args: [{ providedIn: 'root' }]
}], ctorParameters: () => [{ type: i1.FilesService }, { type: i2.InventoryService }, { type: i2.InventoryBinaryService }, { type: i1.AlertService }, { type: i3.TranslateService }, { type: i2.FetchClient }, { type: i1.AppStateService }] });
class MarkdownWidgetConfigComponent {
set markdownPreviewTemplate(template) {
this.widgetConfigService.setPreview(template || null);
}
constructor(formBuilder, form, alert, markdownService) {
this.formBuilder = formBuilder;
this.form = form;
this.alert = alert;
this.markdownService = markdownService;
this.MarkdownSourceType = MarkdownSourceType;
this.uploadChoice = MarkdownSourceType.URL;
this.previewMarkdown$ = new BehaviorSubject('');
this.editorContent = '';
this.widgetConfigService = inject(WidgetConfigService);
this.destroy$ = new Subject();
}
async onBeforeSave(config) {
if (this.uploadChoice === MarkdownSourceType.WRITE) {
Object.assign(config, {
markdownContent: this.editorContent,
contentUrl: null,
markdownBinaryId: null
});
return true;
}
if (this.formGroup.invalid) {
return false;
}
if (this.uploadChoice === MarkdownSourceType.URL) {
Object.assign(config, {
contentUrl: this.formGroup.value.contentUrl,
markdownBinaryId: null,
markdownContent: null
});
return true;
}
const fileFromForm = this.getFileFromFormValue(this.formGroup.value);
if (fileFromForm && fileFromForm !== this.fileFromConfig) {
try {
const markdownBinaryId = await this.markdownService.uploadFile(fileFromForm);
Object.assign(config, { markdownBinaryId, contentUrl: null, markdownContent: null });
return true;
}
catch (e) {
this.alert.danger(gettext('Unable to upload Markdown file.'), e?.data);
return false;
}
}
if (!fileFromForm) {
Object.assign(config, {
contentUrl: '/readme.md',
markdownBinaryId: null,
markdownContent: null
});
}
return true;
}
async ngOnInit() {
// Determine initial mode from config
if (this.config.markdownContent) {
this.uploadChoice = MarkdownSourceType.WRITE;
}
else if (this.config.markdownBinaryId) {
this.uploadChoice = MarkdownSourceType.UPLOAD;
}
this.initForm();
// Load initial content based on mode
switch (this.uploadChoice) {
case MarkdownSourceType.WRITE:
this.editorContent = this.config.markdownContent;
this.previewMarkdown$.next(this.config.markdownContent);
break;
case MarkdownSourceType.UPLOAD:
this.fileFromConfig = await this.markdownService.getFile(this.config.markdownBinaryId);
this.formGroup.patchValue({
droppedFile: [{ file: this.fileFromConfig, name: this.fileFromConfig.name }]
});
await this.updatePreviewFromFile(this.fileFromConfig);
break;
case MarkdownSourceType.URL:
if (this.config.contentUrl) {
this.previewMarkdown$.next(await this.markdownService.getContentFromUrl(this.config.contentUrl));
}
break;
}
this.formGroup.valueChanges.pipe(takeUntil(this.destroy$)).subscribe(async (value) => {
await this.updatePreview(value);
});
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
onChange(value) {
this.uploadChoice = value;
// Ensure dropped file has 'name' property for drop area to display filename
const droppedFile = this.formGroup.value.droppedFile;
if (value === MarkdownSourceType.UPLOAD && droppedFile?.[0]?.file) {
const normalizedFile = droppedFile.map(item => ({
...item,
name: item.name || item.file?.name
}));
this.formGroup.controls['droppedFile'].setValue(normalizedFile);
}
this.formGroup.updateValueAndValidity();
}
onEditorChange(content) {
this.editorContent = content;
this.previewMarkdown$.next(content);
}
async updatePreview(formValue) {
const choice = formValue.uploadChoice || this.uploadChoice;
switch (choice) {
case MarkdownSourceType.WRITE:
if (!this.editorContent) {
this.editorContent = this.previewMarkdown$.getValue();
}
this.previewMarkdown$.next(this.editorContent);
break;
case MarkdownSourceType.UPLOAD:
const file = this.getFileFromFormValue(formValue);
file ? await this.updatePreviewFromFile(file) : this.previewMarkdown$.next('');
break;
case MarkdownSourceType.URL:
if (formValue.contentUrl) {
this.previewMarkdown$.next(await this.markdownService.getContentFromUrl(formValue.contentUrl));
}
else {
this.previewMarkdown$.next('');
}
break;
}
}
async updatePreviewFromFile(file) {
try {
this.previewMarkdown$.next(await file.text());
}
catch {
this.previewMarkdown$.next('');
}
}
getFileFromFormValue(formValue) {
const binary = formValue?.droppedFile || [];
return binary[0]?.file || null;
}
initForm() {
this.formGroup = this.formBuilder.group({
contentUrl: this.formBuilder.nonNullable.control('', [Validators.maxLength(2000)]),
droppedFile: this.formBuilder.control(null, [
Validators.minLength(1),
Validators.maxLength(1),
C8yValidators.filesValidator({ maximumFileSizeInKb: 1000 })
]),
uploadChoice: this.formBuilder.nonNullable.control(this.uploadChoice)
}, { validators: this.requireValidSource() });
this.form.form.addControl('config', this.formGroup);
this.formGroup.patchValue(this.config);
}
requireValidSource() {
return (control) => {
const url = control.get('contentUrl');
const droppedFile = control.get('droppedFile');
// Write mode - always valid, editor content handled separately
if (this.uploadChoice === MarkdownSourceType.WRITE) {
url.setErrors(null);
droppedFile.setErrors(null);
return null;
}
// Clear required errors on inactive controls, set on active if empty
if (this.uploadChoice === MarkdownSourceType.URL) {
this.clearRequiredError(droppedFile);
if (!url.value) {
url.setErrors({ ...url.errors, required: true });
return { required: true };
}
this.clearRequiredError(url);
}
if (this.uploadChoice === MarkdownSourceType.UPLOAD) {
this.clearRequiredError(url);
if (!droppedFile.value) {
droppedFile.setErrors({ ...droppedFile.errors, required: true });
return { required: true };
}
this.clearRequiredError(droppedFile);
}
return null;
};
}
clearRequiredError(control) {
if (control?.errors?.required) {
const { required: _, ...rest } = control.errors;
control.setErrors(Object.keys(rest).length ? rest : null);
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: MarkdownWidgetConfigComponent, deps: [{ token: i1$1.FormBuilder }, { token: i1$1.NgForm }, { token: i1.AlertService }, { token: MarkdownWidgetService }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: MarkdownWidgetConfigComponent, isStandalone: true, selector: "c8y-markdown-widget-config", inputs: { config: "config" }, viewQueries: [{ propertyName: "markdownPreviewTemplate", first: true, predicate: ["markdownPreview"], descendants: true }], ngImport: i0, template: "<fieldset class=\"c8y-fieldset\">\n <legend>{{ 'Source' | translate }}</legend>\n <form [formGroup]=\"formGroup\">\n <div class=\"form-group\">\n <label\n class=\"c8y-radio radio-inline\"\n title=\"{{ 'Write Markdown' | translate }}\"\n >\n <input\n name=\"uploadChoice\"\n type=\"radio\"\n [value]=\"MarkdownSourceType.WRITE\"\n formControlName=\"uploadChoice\"\n (change)=\"onChange(MarkdownSourceType.WRITE)\"\n />\n <span></span>\n <span>{{ 'Write Markdown' | translate }}</span>\n </label>\n <label\n class=\"c8y-radio radio-inline m-l-8\"\n title=\"{{ 'Upload a binary' | translate }}\"\n >\n <input\n name=\"uploadChoice\"\n type=\"radio\"\n [value]=\"MarkdownSourceType.UPLOAD\"\n formControlName=\"uploadChoice\"\n (change)=\"onChange(MarkdownSourceType.UPLOAD)\"\n />\n <span></span>\n <span>{{ 'Upload a binary' | translate }}</span>\n </label>\n <label\n class=\"c8y-radio radio-inline m-l-8\"\n title=\"{{ 'Provide a file path' | translate }}\"\n >\n <input\n name=\"uploadChoice\"\n type=\"radio\"\n [value]=\"MarkdownSourceType.URL\"\n formControlName=\"uploadChoice\"\n (change)=\"onChange(MarkdownSourceType.URL)\"\n />\n <span></span>\n <span>{{ 'Provide a file path' | translate }}</span>\n </label>\n </div>\n @switch (uploadChoice) {\n @case (MarkdownSourceType.WRITE) {\n <c8y-editor\n class=\"d-block\"\n style=\"height: 300px\"\n [ngModel]=\"editorContent\"\n (ngModelChange)=\"onEditorChange($event)\"\n [ngModelOptions]=\"{ standalone: true }\"\n [editorOptions]=\"{\n language: 'markdown',\n tabSize: 2,\n insertSpaces: true,\n minimap: { enabled: false },\n wordWrap: 'on'\n }\"\n ></c8y-editor>\n }\n @case (MarkdownSourceType.UPLOAD) {\n <c8y-form-group class=\"m-b-24\">\n <c8y-drop-area\n class=\"drop-area-sm\"\n [title]=\"'Drop file or click to browse' | translate\"\n formControlName=\"droppedFile\"\n [maxAllowedFiles]=\"1\"\n [accept]=\"'md'\"\n ></c8y-drop-area>\n </c8y-form-group>\n }\n @case (MarkdownSourceType.URL) {\n <c8y-form-group class=\"m-b-24\">\n <div class=\"input-group\">\n <span class=\"input-group-addon\">\n <i c8yIcon=\"globe\"></i>\n </span>\n <input\n class=\"form-control\"\n placeholder=\"{{ 'e.g.' | translate }} http://example.com/binary.zip\"\n type=\"text\"\n formControlName=\"contentUrl\"\n />\n </div>\n </c8y-form-group>\n }\n }\n </form>\n</fieldset>\n\n<ng-template #markdownPreview>\n @if (previewMarkdown$ | async; as previewMarkdown) {\n <div\n class=\"p-16 p-t-0 markdown-content fit-h overflow-auto\"\n [innerHTML]=\"previewMarkdown | markdownToHtml | async\"\n ></div>\n } @else {\n <div class=\"fit-h d-flex d-col j-c-center a-i-center\">\n <c8y-ui-empty-state\n [icon]=\"'file-text'\"\n [title]=\"'No content to preview' | translate\"\n [subtitle]=\"\n 'Write Markdown, upload a file, or provide a URL to see the preview' | translate\n \"\n [horizontal]=\"false\"\n ></c8y-ui-empty-state>\n </div>\n }\n</ng-template>\n", dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.RadioControlValueAccessor, selector: "input[type=radio][formControlName],input[type=radio][formControl],input[type=radio][ngModel]", inputs: ["name", "formControlName", "value"] }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: FormGroupComponent, selector: "c8y-form-group", inputs: ["hasError", "hasWarning", "hasSuccess", "novalidation", "status"] }, { kind: "component", type: DropAreaComponent, selector: "c8y-drop-area", inputs: ["formControl", "title", "message", "icon", "loadingMessage", "forceHideList", "alwaysShow", "clickToOpen", "loading", "progress", "maxAllowedFiles", "files", "maxFileSizeInMegaBytes", "accept"], outputs: ["dropped"] }, { kind: "component", type: EmptyStateComponent, selector: "c8y-ui-empty-state", inputs: ["icon", "title", "subtitle", "horizontal"] }, { kind: "directive", type: IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "component", type: EditorComponent, selector: "c8y-editor", inputs: ["editorOptions", "theme"], outputs: ["editorInit"] }, { kind: "pipe", type: AsyncPipe, name: "async" }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }, { kind: "pipe", type: MarkdownToHtmlPipe, name: "markdownToHtml" }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: MarkdownWidgetConfigComponent, decorators: [{
type: Component,
args: [{ selector: 'c8y-markdown-widget-config', standalone: true, imports: [
ReactiveFormsModule,
FormsModule,
AsyncPipe,
C8yTranslatePipe,
MarkdownToHtmlPipe,
FormGroupComponent,
DropAreaComponent,
EmptyStateComponent,
IconDirective,
EditorComponent
], template: "<fieldset class=\"c8y-fieldset\">\n <legend>{{ 'Source' | translate }}</legend>\n <form [formGroup]=\"formGroup\">\n <div class=\"form-group\">\n <label\n class=\"c8y-radio radio-inline\"\n title=\"{{ 'Write Markdown' | translate }}\"\n >\n <input\n name=\"uploadChoice\"\n type=\"radio\"\n [value]=\"MarkdownSourceType.WRITE\"\n formControlName=\"uploadChoice\"\n (change)=\"onChange(MarkdownSourceType.WRITE)\"\n />\n <span></span>\n <span>{{ 'Write Markdown' | translate }}</span>\n </label>\n <label\n class=\"c8y-radio radio-inline m-l-8\"\n title=\"{{ 'Upload a binary' | translate }}\"\n >\n <input\n name=\"uploadChoice\"\n type=\"radio\"\n [value]=\"MarkdownSourceType.UPLOAD\"\n formControlName=\"uploadChoice\"\n (change)=\"onChange(MarkdownSourceType.UPLOAD)\"\n />\n <span></span>\n <span>{{ 'Upload a binary' | translate }}</span>\n </label>\n <label\n class=\"c8y-radio radio-inline m-l-8\"\n title=\"{{ 'Provide a file path' | translate }}\"\n >\n <input\n name=\"uploadChoice\"\n type=\"radio\"\n [value]=\"MarkdownSourceType.URL\"\n formControlName=\"uploadChoice\"\n (change)=\"onChange(MarkdownSourceType.URL)\"\n />\n <span></span>\n <span>{{ 'Provide a file path' | translate }}</span>\n </label>\n </div>\n @switch (uploadChoice) {\n @case (MarkdownSourceType.WRITE) {\n <c8y-editor\n class=\"d-block\"\n style=\"height: 300px\"\n [ngModel]=\"editorContent\"\n (ngModelChange)=\"onEditorChange($event)\"\n [ngModelOptions]=\"{ standalone: true }\"\n [editorOptions]=\"{\n language: 'markdown',\n tabSize: 2,\n insertSpaces: true,\n minimap: { enabled: false },\n wordWrap: 'on'\n }\"\n ></c8y-editor>\n }\n @case (MarkdownSourceType.UPLOAD) {\n <c8y-form-group class=\"m-b-24\">\n <c8y-drop-area\n class=\"drop-area-sm\"\n [title]=\"'Drop file or click to browse' | translate\"\n formControlName=\"droppedFile\"\n [maxAllowedFiles]=\"1\"\n [accept]=\"'md'\"\n ></c8y-drop-area>\n </c8y-form-group>\n }\n @case (MarkdownSourceType.URL) {\n <c8y-form-group class=\"m-b-24\">\n <div class=\"input-group\">\n <span class=\"input-group-addon\">\n <i c8yIcon=\"globe\"></i>\n </span>\n <input\n class=\"form-control\"\n placeholder=\"{{ 'e.g.' | translate }} http://example.com/binary.zip\"\n type=\"text\"\n formControlName=\"contentUrl\"\n />\n </div>\n </c8y-form-group>\n }\n }\n </form>\n</fieldset>\n\n<ng-template #markdownPreview>\n @if (previewMarkdown$ | async; as previewMarkdown) {\n <div\n class=\"p-16 p-t-0 markdown-content fit-h overflow-auto\"\n [innerHTML]=\"previewMarkdown | markdownToHtml | async\"\n ></div>\n } @else {\n <div class=\"fit-h d-flex d-col j-c-center a-i-center\">\n <c8y-ui-empty-state\n [icon]=\"'file-text'\"\n [title]=\"'No content to preview' | translate\"\n [subtitle]=\"\n 'Write Markdown, upload a file, or provide a URL to see the preview' | translate\n \"\n [horizontal]=\"false\"\n ></c8y-ui-empty-state>\n </div>\n }\n</ng-template>\n" }]
}], ctorParameters: () => [{ type: i1$1.FormBuilder }, { type: i1$1.NgForm }, { type: i1.AlertService }, { type: MarkdownWidgetService }], propDecorators: { config: [{
type: Input
}], markdownPreviewTemplate: [{
type: ViewChild,
args: ['markdownPreview']
}] } });
class MarkdownWidgetViewComponent {
constructor(markdownWidgetService) {
this.markdownWidgetService = markdownWidgetService;
}
async ngOnInit() {
if (this.config.markdownContent) {
this.markdown = this.config.markdownContent;
}
else if (this.config.markdownBinaryId) {
const file = await this.markdownWidgetService.getFile(this.config.markdownBinaryId);
this.markdown = await file?.text();
}
else if (this.config.contentUrl) {
this.markdown = await this.markdownWidgetService.getContentFromUrl(this.config.contentUrl);
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: MarkdownWidgetViewComponent, deps: [{ token: MarkdownWidgetService }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.19", type: MarkdownWidgetViewComponent, isStandalone: true, selector: "c8y-markdown-widget-view", inputs: { config: "config" }, ngImport: i0, template: "<div id=\"helpContent\" class=\"p-16 p-t-0 markdown-content\" [innerHTML]=\"markdown | markdownToHtml | async\"></div>\n", dependencies: [{ kind: "pipe", type: MarkdownToHtmlPipe, name: "markdownToHtml" }, { kind: "pipe", type: AsyncPipe, name: "async" }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: MarkdownWidgetViewComponent, decorators: [{
type: Component,
args: [{ selector: 'c8y-markdown-widget-view', standalone: true, imports: [MarkdownToHtmlPipe, AsyncPipe], template: "<div id=\"helpContent\" class=\"p-16 p-t-0 markdown-content\" [innerHTML]=\"markdown | markdownToHtml | async\"></div>\n" }]
}], ctorParameters: () => [{ type: MarkdownWidgetService }], propDecorators: { config: [{
type: Input
}] } });
/**
* Generated bundle index. Do not edit.
*/
export { MarkdownSourceType, MarkdownWidgetConfigComponent, MarkdownWidgetService, MarkdownWidgetViewComponent };
//# sourceMappingURL=c8y-ngx-components-widgets-implementations-markdown.mjs.map