diff --git a/core/gui/angular.json b/core/gui/angular.json
index a76ee354362..70475bba4f5 100644
--- a/core/gui/angular.json
+++ b/core/gui/angular.json
@@ -29,10 +29,15 @@
"node_modules/jointjs/css/themes/default.css",
"node_modules/ng-zorro-antd/ng-zorro-antd.min.css",
"node_modules/ng-zorro-antd/resizable/style/index.min.css",
+ "node_modules/bootstrap/dist/css/bootstrap.css",
+ "node_modules/bootstrap-markdown/css/bootstrap-markdown.min.css",
+ "node_modules/font-awesome/css/font-awesome.css",
"src/styles.scss"
],
"scripts": [
- "./node_modules/marked/marked.min.js"
+ "./node_modules/marked/marked.min.js",
+ "node_modules/jquery/dist/jquery.js",
+ "node_modules/bootstrap-markdown/js/bootstrap-markdown.js"
],
"customWebpackConfig": {
"path": "./custom-webpack.config.js"
diff --git a/core/gui/package.json b/core/gui/package.json
index e8aefd32f05..c1839bdcb73 100644
--- a/core/gui/package.json
+++ b/core/gui/package.json
@@ -46,16 +46,21 @@
"@types/lodash-es": "4.17.4",
"@types/plotly.js-basic-dist-min": "2.12.4",
"ajv": "8.10.0",
+ "angular-markdown-editor": "^3.1.1",
"backbone": "1.4.1",
+ "bootstrap": "^5.3.7",
+ "bootstrap-markdown": "^2.10.0",
"content-disposition": "0.5.4",
"dagre": "0.8.5",
"deep-map": "2.0.0",
"edit-distance": "1.0.4",
"es6-weak-map": "2.0.3",
"file-saver": "2.0.5",
+ "font-awesome": "^4.7.0",
"fuse.js": "6.5.3",
"html2canvas": "1.4.1",
"jointjs": "3.5.4",
+ "jquery": "^3.7.1",
"js-abbreviation-number": "1.4.0",
"jszip": "3.10.1",
"lodash-es": "4.17.21",
@@ -111,11 +116,13 @@
"@nrwl/nx-cloud": "19.1.0",
"@nx/angular": "20.0.3",
"@types/backbone": "1.4.15",
+ "@types/bootstrap": "^5",
"@types/content-disposition": "0",
"@types/dagre": "0.7.47",
"@types/file-saver": "2.0.5",
"@types/graphlib": "2.1.8",
"@types/jasmine": "4.6.4",
+ "@types/jquery": "^3",
"@types/json-schema": "7.0.9",
"@types/lodash": "4.14.179",
"@types/node": "18.15.5",
diff --git a/core/gui/src/app/app.module.ts b/core/gui/src/app/app.module.ts
index 13759e6e55a..e61ea791cde 100644
--- a/core/gui/src/app/app.module.ts
+++ b/core/gui/src/app/app.module.ts
@@ -172,6 +172,8 @@ import { AdminSettingsComponent } from "./dashboard/component/admin/settings/adm
import { catchError, of } from "rxjs";
import { FormlyRepeatDndComponent } from "./common/formly/repeat-dnd/repeat-dnd.component";
import { NzInputNumberModule } from "ng-zorro-antd/input-number";
+import { UserDatasetFileEditorComponent } from "./dashboard/component/user/user-dataset/user-dataset-explorer/user-dataset-file-editor/user-dataset-file-editor.component";
+import { AngularMarkdownEditorModule } from "angular-markdown-editor";
registerLocaleData(en);
@@ -264,6 +266,7 @@ registerLocaleData(en);
HubSearchResultComponent,
ComputingUnitSelectionComponent,
AdminSettingsComponent,
+ UserDatasetFileEditorComponent,
],
imports: [
BrowserModule,
@@ -330,6 +333,7 @@ registerLocaleData(en);
NzDividerModule,
NzProgressModule,
NzInputNumberModule,
+ AngularMarkdownEditorModule.forRoot(),
],
providers: [
provideNzI18n(en_US),
diff --git a/core/gui/src/app/dashboard/component/user/user-dataset/user-dataset-explorer/dataset-detail.component.html b/core/gui/src/app/dashboard/component/user/user-dataset/user-dataset-explorer/dataset-detail.component.html
index d4dddf94f6d..d30dcab51fc 100644
--- a/core/gui/src/app/dashboard/component/user/user-dataset/user-dataset-explorer/dataset-detail.component.html
+++ b/core/gui/src/app/dashboard/component/user/user-dataset/user-dataset-explorer/dataset-detail.component.html
@@ -113,6 +113,18 @@
+
+
+
+
+
+
Choose a Version:
+
+
+
+
+
+
{
+ if (this.versions.length > 0) {
+ // Select the latest version (newly created)
+ this.selectedVersion = this.versions[0];
+
+ // Refresh the file tree for the new version
+ if (this.did && this.selectedVersion.dvid) {
+ this.datasetService
+ .retrieveDatasetVersionFileTree(this.did, this.selectedVersion.dvid, this.isLogin)
+ .pipe(untilDestroyed(this))
+ .subscribe(data => {
+ this.fileTreeNodeList = data.fileNodes;
+ this.currentDatasetVersionSize = data.size;
+
+ // Try to find and re-select the same file we were editing
+ const fileNode = this.findFileInTree(currentFileName);
+ if (fileNode) {
+ this.loadFileContent(fileNode);
+ } else {
+ // Fallback to first file if our file isn't found
+ let currentNode = this.fileTreeNodeList[0];
+ while (currentNode && currentNode.type === "directory" && currentNode.children) {
+ currentNode = currentNode.children[0];
+ }
+ if (currentNode) {
+ this.loadFileContent(currentNode);
+ }
+ }
+ });
+ }
+ }
+ }, 500); // Small delay to ensure backend has processed the new version
+
+ this.exitEditMode();
+ }
+
+ private findFileInTree(fileName: string, nodes: DatasetFileNode[] = this.fileTreeNodeList): DatasetFileNode | null {
+ for (const node of nodes) {
+ if (node.name === fileName && node.type === "file") {
+ return node;
+ }
+ if (node.children) {
+ const found = this.findFileInTree(fileName, node.children);
+ if (found) {
+ return found;
+ }
+ }
+ }
+ return null;
+ }
+
+ public onClickCreateReadme(): void {
+ this.modalService.confirm({
+ nzTitle: "Create README.md",
+ nzContent: "Are you sure you want to create a README.md file for this dataset?",
+ nzOkText: "Yes, Create",
+ nzCancelText: "Cancel",
+ nzOnOk: () => {
+ this.createReadmeFile();
+ },
+ });
+ }
+
+ private createReadmeFile(): void {
+ if (!this.did) return;
+
+ this.isCreatingReadme = true;
+ const defaultReadmeContent = "# Dataset README\n\nDescribe your dataset here...";
+
+ this.datasetService
+ .getDataset(this.did, this.isLogin)
+ .pipe(
+ switchMap(dashboardDataset => {
+ const datasetName = dashboardDataset.dataset.name;
+ const readmeBlob = new Blob([defaultReadmeContent], { type: "text/markdown" });
+ const readmeFile = new File([readmeBlob], "README.md", { type: "text/markdown" });
+ return this.datasetService.multipartUpload(
+ datasetName,
+ "README.md",
+ readmeFile,
+ this.chunkSizeMB * 1024 * 1024,
+ this.maxConcurrentChunks
+ );
+ }),
+ switchMap(progress => {
+ if (progress.status === "finished") {
+ return this.datasetService.createDatasetVersion(this.did!, "Created README.md");
+ }
+ return of(progress);
+ }),
+ untilDestroyed(this)
+ )
+ .subscribe({
+ next: result => {
+ if (result && typeof result === "object" && "dvid" in result) {
+ this.isCreatingReadme = false;
+ this.notificationService.success("README created successfully!");
+
+ this.currentDisplayedFileName = "README.md";
+ this.onFileChanged();
+
+ setTimeout(() => {
+ this.isEditMode = true;
+ }, 1000);
+ }
+ },
+ error: (error: unknown) => {
+ this.isCreatingReadme = false;
+ console.error("Error creating README:", error);
+ this.notificationService.error("Failed to create README");
+ },
+ });
+ }
+
+ public hasReadmeFile(): boolean {
+ return this.findFileInTree("README.md") !== null;
+ }
+
+ public isEditableFile(fileName: string): boolean {
+ const extension = fileName.toLowerCase().split(".").pop();
+ const editableExtensions = ["md", "markdown", "txt", "log", "yml", "yaml"];
+ return editableExtensions.includes(extension || "");
+ }
+
+ public onClickEditFile(): void {
+ if (!this.selectedVersion || !this.currentDisplayedFileName) return;
+
+ this.isEditMode = !this.isEditMode;
+ }
+
+ public exitEditMode(): void {
+ this.isEditMode = false;
+ }
+
onPublicStatusChange(checked: boolean): void {
// Handle the change in dataset public status
if (this.did) {
@@ -341,6 +488,8 @@ export class DatasetDetailComponent implements OnInit {
}
onVersionSelected(version: DatasetVersion): void {
+ this.exitEditMode();
+
this.selectedVersion = version;
if (this.did && this.selectedVersion.dvid)
this.datasetService
@@ -362,6 +511,7 @@ export class DatasetDetailComponent implements OnInit {
}
onVersionFileTreeNodeSelected(node: DatasetFileNode) {
+ this.exitEditMode();
this.loadFileContent(node);
}
diff --git a/core/gui/src/app/dashboard/component/user/user-dataset/user-dataset-explorer/user-dataset-file-editor/user-dataset-file-editor.component.html b/core/gui/src/app/dashboard/component/user/user-dataset/user-dataset-explorer/user-dataset-file-editor/user-dataset-file-editor.component.html
new file mode 100644
index 00000000000..09d9d8cf0df
--- /dev/null
+++ b/core/gui/src/app/dashboard/component/user/user-dataset/user-dataset-explorer/user-dataset-file-editor/user-dataset-file-editor.component.html
@@ -0,0 +1,120 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/core/gui/src/app/dashboard/component/user/user-dataset/user-dataset-explorer/user-dataset-file-editor/user-dataset-file-editor.component.scss b/core/gui/src/app/dashboard/component/user/user-dataset/user-dataset-explorer/user-dataset-file-editor/user-dataset-file-editor.component.scss
new file mode 100644
index 00000000000..b8da4a680ea
--- /dev/null
+++ b/core/gui/src/app/dashboard/component/user/user-dataset/user-dataset-explorer/user-dataset-file-editor/user-dataset-file-editor.component.scss
@@ -0,0 +1,99 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+.file-loading {
+ padding: 16px;
+ text-align: center;
+}
+
+.file-content {
+ /* Container for file editing interface */
+}
+
+.file-expanded {
+ border: 1px solid #e8e8e8;
+ border-radius: 6px;
+ background: white;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+}
+
+.file-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 12px 16px;
+ border-bottom: 1px solid #f0f0f0;
+ background: #fafafa;
+ border-radius: 6px 6px 0 0;
+}
+
+.file-title {
+ margin: 0;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ font-size: 16px;
+ font-weight: 600;
+ color: #262626;
+}
+
+.file-title i {
+ color: #1890ff;
+}
+
+.file-controls {
+ display: flex;
+ gap: 8px;
+ align-items: center;
+}
+
+.unsaved-indicator {
+ color: #fa8c16;
+ font-size: 12px;
+ display: flex;
+ align-items: center;
+ gap: 4px;
+}
+
+.file-editor-container {
+ /* Container for different editor types */
+}
+
+.markdown-editor-wrapper {
+ /* Wrapper for angular-markdown-editor */
+}
+
+.simple-editor .file-textarea.text-editor {
+ width: 100%;
+ min-height: 300px;
+ padding: 16px;
+ border: 1px solid #d9d9d9;
+ border-radius: 0 0 6px 6px;
+ font-family: "Monaco", "Menlo", "Ubuntu Mono", monospace;
+ font-size: 14px;
+ line-height: 1.6;
+ resize: vertical;
+ background: white;
+}
+
+.simple-editor .file-textarea.text-editor:focus {
+ border-color: #1890ff;
+ box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
+ outline: none;
+}
diff --git a/core/gui/src/app/dashboard/component/user/user-dataset/user-dataset-explorer/user-dataset-file-editor/user-dataset-file-editor.component.ts b/core/gui/src/app/dashboard/component/user/user-dataset/user-dataset-explorer/user-dataset-file-editor/user-dataset-file-editor.component.ts
new file mode 100644
index 00000000000..6693998e938
--- /dev/null
+++ b/core/gui/src/app/dashboard/component/user/user-dataset/user-dataset-explorer/user-dataset-file-editor/user-dataset-file-editor.component.ts
@@ -0,0 +1,306 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import {
+ Component,
+ ElementRef,
+ EventEmitter,
+ Input,
+ OnChanges,
+ OnInit,
+ Output,
+ SimpleChanges,
+ ViewChild,
+ ViewEncapsulation,
+} from "@angular/core";
+import { DatasetService } from "../../../../../service/user/dataset/dataset.service";
+import { UntilDestroy, untilDestroyed } from "@ngneat/until-destroy";
+import { NotificationService } from "../../../../../../common/service/notification/notification.service";
+import { switchMap } from "rxjs/operators";
+import { of } from "rxjs";
+import { EditorInstance, EditorOption } from "angular-markdown-editor";
+import { MarkdownService } from "ngx-markdown";
+
+@UntilDestroy()
+@Component({
+ selector: "texera-user-dataset-file-editor",
+ templateUrl: "./user-dataset-file-editor.component.html",
+ styleUrls: ["./user-dataset-file-editor.component.scss"],
+ encapsulation: ViewEncapsulation.None,
+})
+export class UserDatasetFileEditorComponent implements OnInit, OnChanges {
+ @Input() did: number | undefined;
+ @Input() dvid: number | undefined;
+ @Input() selectedVersion: any | undefined;
+ @Input() datasetName: string = "";
+ @Input() filePath: string = "";
+ @Input() isMaximized: boolean = false;
+ @Input() userHasWriteAccess: boolean = false;
+ @Input() isLogin: boolean = true;
+ @Input() chunkSizeMB!: number;
+ @Input() maxConcurrentChunks!: number;
+ @Input() isEditMode: boolean = true;
+ @Output() userMakeChanges = new EventEmitter();
+ @Output() editCanceled = new EventEmitter();
+
+ @ViewChild("fileTextarea") fileTextarea!: ElementRef;
+
+ public fileContent: string = "";
+ public fileExists: boolean = false;
+ public isLoading: boolean = false;
+ public editingContent: string = "";
+ public fileType: "markdown" | "text" | "unsupported" = "unsupported";
+ public showFileContent: boolean = false;
+
+ // Angular Markdown Editor properties
+ public bsEditorInstance!: EditorInstance;
+ public editorOptions!: EditorOption;
+
+ constructor(
+ private datasetService: DatasetService,
+ private notificationService: NotificationService,
+ private markdownService: MarkdownService
+ ) {}
+
+ ngOnInit(): void {
+ this.initializeEditorOptions();
+
+ if (this.dvid && this.datasetName && this.selectedVersion && this.filePath) {
+ this.determineFileType();
+ this.loadFile();
+ this.isEditMode = true;
+ }
+ }
+
+ ngOnChanges(changes: SimpleChanges): void {
+ if (
+ (changes["dvid"] || changes["datasetName"] || changes["selectedVersion"] || changes["filePath"]) &&
+ this.dvid &&
+ this.datasetName &&
+ this.selectedVersion &&
+ this.filePath
+ ) {
+ this.isEditMode = false;
+ this.showFileContent = false;
+ this.isLoading = false;
+ this.fileExists = false;
+ this.fileContent = "";
+ this.editingContent = "";
+
+ this.determineFileType();
+ this.loadFile();
+ }
+ }
+
+ private initializeEditorOptions(): void {
+ this.editorOptions = {
+ autofocus: false,
+ iconlibrary: "fa",
+ savable: false,
+ onShow: (e: EditorInstance) => {
+ this.bsEditorInstance = e;
+ console.log("Markdown editor initialized");
+ },
+ onChange: (e: EditorInstance) => {
+ this.editingContent = e.getContent();
+ },
+ parser: (val: string) => this.parseMarkdown(val),
+ };
+ }
+
+ private determineFileType(): void {
+ const extension = this.filePath.toLowerCase().split(".").pop();
+ switch (extension) {
+ case "md":
+ case "markdown":
+ this.fileType = "markdown";
+ break;
+ case "txt":
+ case "log":
+ case "yml":
+ case "yaml":
+ this.fileType = "text";
+ break;
+ default:
+ this.fileType = "unsupported";
+ }
+ }
+
+ private loadFile(): void {
+ if (!this.did || !this.dvid || !this.datasetName || !this.selectedVersion || !this.filePath) return;
+
+ this.isLoading = true;
+
+ this.datasetService
+ .retrieveDatasetVersionSingleFile(this.filePath, this.isLogin)
+ .pipe(
+ switchMap(blob => {
+ return new Promise((resolve, reject) => {
+ const reader = new FileReader();
+ reader.onload = () => resolve(reader.result as string);
+ reader.onerror = () => reject(reader.error);
+ reader.readAsText(blob);
+ });
+ }),
+ untilDestroyed(this)
+ )
+ .subscribe({
+ next: content => {
+ this.isLoading = false;
+ this.fileExists = true;
+ this.fileContent = content;
+ this.editingContent = content;
+ },
+ error: () => {
+ this.isLoading = false;
+ this.fileExists = false;
+ this.fileContent = "";
+ this.editingContent = "";
+ console.log("File not found or error loading");
+ },
+ });
+ }
+
+ public cancelEditing(): void {
+ this.editingContent = this.fileContent;
+ this.isEditMode = false;
+ this.editCanceled.emit();
+ }
+
+ public onMarkdownEditorChange(event: any): void {
+ if (event && event.detail && event.detail.eventData) {
+ this.editingContent = event.detail.eventData.getContent();
+ } else {
+ // Handle direct content change
+ this.editingContent = event;
+ }
+ }
+
+ public onEditorKeydown(event: KeyboardEvent): void {
+ if ((event.ctrlKey || event.metaKey) && event.key === "s") {
+ event.preventDefault();
+ this.saveFile();
+ }
+
+ if (event.key === "Tab") {
+ event.preventDefault();
+ const textarea = event.target as HTMLTextAreaElement;
+ const start = textarea.selectionStart;
+ const end = textarea.selectionEnd;
+
+ const value = textarea.value;
+ textarea.value = value.substring(0, start) + " " + value.substring(end);
+
+ textarea.selectionStart = textarea.selectionEnd = start + 2;
+
+ this.editingContent = textarea.value;
+ }
+ }
+
+ public saveFile(): void {
+ if (!this.did || !this.userHasWriteAccess) return;
+
+ if (this.editingContent === this.fileContent) {
+ this.notificationService.warning("No changes detected in file content");
+ return;
+ }
+
+ this.uploadFileContent(this.editingContent, `${this.getFileName()} updated successfully`);
+ }
+
+ private uploadFileContent(content: string, successMessage: string): void {
+ if (!this.did) return;
+
+ this.datasetService
+ .getDataset(this.did, this.isLogin)
+ .pipe(
+ switchMap(dashboardDataset => {
+ const datasetName = dashboardDataset.dataset.name;
+ const fileName = this.getFileName();
+
+ const mimeType = this.getMimeType();
+ const fileBlob = new Blob([content], { type: mimeType });
+ const file = new File([fileBlob], fileName, { type: mimeType });
+
+ return this.datasetService.multipartUpload(datasetName, fileName, file, 50 * 1024 * 1024, 10);
+ }),
+ switchMap(progress => {
+ if (progress.status === "finished") {
+ const fileName = this.getFileName();
+ const versionMessage = successMessage.includes("created") ? `Created ${fileName}` : `Updated ${fileName}`;
+
+ return this.datasetService.createDatasetVersion(this.did!, versionMessage);
+ }
+ return of(progress);
+ }),
+ untilDestroyed(this)
+ )
+ .subscribe({
+ next: result => {
+ if (result && typeof result === "object" && "dvid" in result) {
+ this.fileExists = true;
+ this.fileContent = content;
+ this.isEditMode = false;
+ this.notificationService.success(successMessage);
+ this.userMakeChanges.emit();
+ }
+ },
+ error: (error: unknown) => {
+ console.error("Error uploading file:", error);
+ this.notificationService.error(`Failed to save ${this.getFileName()}`);
+ },
+ });
+ }
+
+ private getMimeType(): string {
+ switch (this.fileType) {
+ case "markdown":
+ return "text/markdown";
+ case "text":
+ return "text/plain";
+ default:
+ return "text/plain";
+ }
+ }
+
+ public getFileName(): string {
+ if (!this.filePath) return "";
+ return this.filePath.split("/").pop() || this.filePath;
+ }
+
+ public isEditable(): boolean {
+ return this.fileType === "markdown" || this.fileType === "text";
+ }
+
+ public hasUnsavedChanges(): boolean {
+ return this.editingContent !== this.fileContent;
+ }
+
+ private parseMarkdown(inputValue: string): string {
+ const markedOutput = this.markdownService.parse(inputValue.trim());
+ this.highlightCode();
+ return markedOutput;
+ }
+
+ private highlightCode(): void {
+ setTimeout(() => {
+ this.markdownService.highlight();
+ });
+ }
+}
diff --git a/core/gui/yarn.lock b/core/gui/yarn.lock
index 43ae178efaa..c96f33063e5 100644
--- a/core/gui/yarn.lock
+++ b/core/gui/yarn.lock
@@ -4775,6 +4775,13 @@ __metadata:
languageName: node
linkType: hard
+"@popperjs/core@npm:^2.9.2":
+ version: 2.11.8
+ resolution: "@popperjs/core@npm:2.11.8"
+ checksum: 10c0/4681e682abc006d25eb380d0cf3efc7557043f53b6aea7a5057d0d1e7df849a00e281cd8ea79c902a35a414d7919621fc2ba293ecec05f413598e0b23d5a1e63
+ languageName: node
+ linkType: hard
+
"@prettier/eslint@npm:prettier-eslint@^16.1.0":
version: 16.3.0
resolution: "prettier-eslint@npm:16.3.0"
@@ -5076,6 +5083,15 @@ __metadata:
languageName: node
linkType: hard
+"@types/bootstrap@npm:^5":
+ version: 5.2.10
+ resolution: "@types/bootstrap@npm:5.2.10"
+ dependencies:
+ "@popperjs/core": "npm:^2.9.2"
+ checksum: 10c0/3e978855eb780df3907e8fe991371dc661c7a8c5b9852a10e33bcf6a909bc1481857aa8786d18b3aa828fb28660145fda0c8648265719e8a97a448b9f0158eae
+ languageName: node
+ linkType: hard
+
"@types/connect-history-api-fallback@npm:^1.3.5, @types/connect-history-api-fallback@npm:^1.5.4":
version: 1.5.4
resolution: "@types/connect-history-api-fallback@npm:1.5.4"
@@ -5271,6 +5287,15 @@ __metadata:
languageName: node
linkType: hard
+"@types/jquery@npm:^3":
+ version: 3.5.33
+ resolution: "@types/jquery@npm:3.5.33"
+ dependencies:
+ "@types/sizzle": "npm:*"
+ checksum: 10c0/d96c42762b7370ddf3b81cdad436a79d275e0ff09e2f4d7fbf2bbd8f97acef4110a11f1c3cb683195a1eba4fd9959e59d73f84d56ce2c010907a2bea696eb057
+ languageName: node
+ linkType: hard
+
"@types/json-schema@npm:*, @types/json-schema@npm:^7.0.12, @types/json-schema@npm:^7.0.4, @types/json-schema@npm:^7.0.8, @types/json-schema@npm:^7.0.9":
version: 7.0.15
resolution: "@types/json-schema@npm:7.0.15"
@@ -6407,6 +6432,19 @@ __metadata:
languageName: node
linkType: hard
+"angular-markdown-editor@npm:^3.1.1":
+ version: 3.1.1
+ resolution: "angular-markdown-editor@npm:3.1.1"
+ dependencies:
+ bootstrap: "npm:>=4.6.2"
+ bootstrap-markdown: "github:refactory-id/bootstrap-markdown"
+ font-awesome: "npm:^4.7.0"
+ jquery: "npm:^3.7.0"
+ tslib: "npm:^2.3.0"
+ checksum: 10c0/9109609e447fb9c91132d56d6893d08b779ff639fbe2c10def4cca1d035f34544fd8353266e7363c69cf05bc024fe7c18db8d6e6f328fc21255ca2e35926bc09
+ languageName: node
+ linkType: hard
+
"ansi-colors@npm:4.1.3, ansi-colors@npm:^4.1.1, ansi-colors@npm:^4.1.3":
version: 4.1.3
resolution: "ansi-colors@npm:4.1.3"
@@ -7084,6 +7122,29 @@ __metadata:
languageName: node
linkType: hard
+"bootstrap-markdown@github:refactory-id/bootstrap-markdown":
+ version: 2.10.0
+ resolution: "bootstrap-markdown@https://github.com/refactory-id/bootstrap-markdown.git#commit=a496d34b9bd34451c8315a850472f794c8df7d53"
+ checksum: 10c0/f314c390ad9c6317eaeeceaca38de363c2623bbc94167f72264a115f267bd37c838d75612e767173c7d528e2372a884bd8fbdb8e915bff84e79807adb16108ae
+ languageName: node
+ linkType: hard
+
+"bootstrap-markdown@npm:^2.10.0":
+ version: 2.10.0
+ resolution: "bootstrap-markdown@npm:2.10.0"
+ checksum: 10c0/903c7cc043e58c742f08bd7e226afbfafba324036c237496387ea4267725c957453dbb5809dafdfced2c12f1b3d5759dd083f096ba69447e217a91a6922ce162
+ languageName: node
+ linkType: hard
+
+"bootstrap@npm:>=4.6.2, bootstrap@npm:^5.3.7":
+ version: 5.3.7
+ resolution: "bootstrap@npm:5.3.7"
+ peerDependencies:
+ "@popperjs/core": ^2.11.8
+ checksum: 10c0/019f0d683aec843b9fc0592ae78560cfe286bc8e31e706d40d8c15d390dcca7ab2ffa193a489a74f65ed5596800b9b79da867545ce3bbafca945b630fe0055af
+ languageName: node
+ linkType: hard
+
"brace-expansion@npm:^1.1.7":
version: 1.1.11
resolution: "brace-expansion@npm:1.1.11"
@@ -10623,6 +10684,13 @@ __metadata:
languageName: node
linkType: hard
+"font-awesome@npm:^4.7.0":
+ version: 4.7.0
+ resolution: "font-awesome@npm:4.7.0"
+ checksum: 10c0/1c456e2939c55192eed67db9c0efb8db3e92fd357ca189ca00030eb44acffa1e9f835288d2204c14b9a9c490a7b14b7090dfaff80ded6b2473f50a923dfb41e7
+ languageName: node
+ linkType: hard
+
"for-each@npm:^0.3.3":
version: 0.3.3
resolution: "for-each@npm:0.3.3"
@@ -11287,11 +11355,13 @@ __metadata:
"@nx/angular": "npm:20.0.3"
"@stoplight/json-ref-resolver": "npm:3.1.5"
"@types/backbone": "npm:1.4.15"
+ "@types/bootstrap": "npm:^5"
"@types/content-disposition": "npm:0"
"@types/dagre": "npm:0.7.47"
"@types/file-saver": "npm:2.0.5"
"@types/graphlib": "npm:2.1.8"
"@types/jasmine": "npm:4.6.4"
+ "@types/jquery": "npm:^3"
"@types/json-schema": "npm:7.0.9"
"@types/lodash": "npm:4.14.179"
"@types/lodash-es": "npm:4.17.4"
@@ -11304,8 +11374,11 @@ __metadata:
"@typescript-eslint/eslint-plugin": "npm:7.0.2"
"@typescript-eslint/parser": "npm:7.0.2"
ajv: "npm:8.10.0"
+ angular-markdown-editor: "npm:^3.1.1"
babel-plugin-dynamic-import-node: "npm:2.3.3"
backbone: "npm:1.4.1"
+ bootstrap: "npm:^5.3.7"
+ bootstrap-markdown: "npm:^2.10.0"
concurrently: "npm:7.4.0"
content-disposition: "npm:0.5.4"
dagre: "npm:0.8.5"
@@ -11320,6 +11393,7 @@ __metadata:
eslint-plugin-rxjs: "npm:5.0.3"
eslint-plugin-rxjs-angular: "npm:2.0.1"
file-saver: "npm:2.0.5"
+ font-awesome: "npm:^4.7.0"
fs-extra: "npm:10.0.1"
fuse.js: "npm:6.5.3"
git-describe: "npm:4.1.0"
@@ -11327,6 +11401,7 @@ __metadata:
jasmine-core: "npm:5.4.0"
jasmine-spec-reporter: "npm:7.0.0"
jointjs: "npm:3.5.4"
+ jquery: "npm:^3.7.1"
js-abbreviation-number: "npm:1.4.0"
jszip: "npm:3.10.1"
karma: "npm:6.4.4"
@@ -12580,6 +12655,13 @@ __metadata:
languageName: node
linkType: hard
+"jquery@npm:^3.7.0, jquery@npm:^3.7.1":
+ version: 3.7.1
+ resolution: "jquery@npm:3.7.1"
+ checksum: 10c0/808cfbfb758438560224bf26e17fcd5afc7419170230c810dd11f5c1792e2263e2970cca8d659eb84fcd9acc301edb6d310096e450277d54be4f57071b0c82d9
+ languageName: node
+ linkType: hard
+
"jquery@npm:~3.6.0":
version: 3.6.4
resolution: "jquery@npm:3.6.4"