From 1c0e5777a40988819ee30b14bb34a828692ee602 Mon Sep 17 00:00:00 2001
From: "SOLITONTECH\\vivin.krishna"
<123377523+vivinkrishna-ni@users.noreply.github.com>
Date: Thu, 31 Aug 2023 13:07:54 +0530
Subject: [PATCH 01/19] restructure folder changes
---
package-lock.json | 94 ++--
packages/nimble-components/package.json | 21 +-
.../nimble-components/src/all-components.ts | 4 +-
.../src/rich-text-editor/index.ts | 361 -------------
.../src/rich-text/editor/index.ts | 481 ++++++++++++++++++
.../editor}/specs/README.md | 12 +-
.../specs/spec-images/button-state.png | Bin
.../specs/spec-images/editor-sample.png | Bin
.../specs/spec-images/viewer-sample.png | Bin
.../editor}/styles.ts | 99 +++-
.../editor}/template.ts | 26 +-
.../testing/rich-text-editor.pageobject.ts | 71 ++-
.../editor}/testing/types.ts | 0
.../tests/rich-text-editor-matrix.stories.ts | 98 +++-
.../editor}/tests/rich-text-editor.spec.ts | 233 ++++++++-
.../editor}/tests/rich-text-editor.stories.ts | 53 +-
.../editor}/tests/types.spec.ts | 0
.../models/markdown-parser.ts} | 89 +---
.../rich-text/models/markdown-serializer.ts | 47 ++
.../src/rich-text/viewer/index.ts | 75 +++
.../viewer}/specs/README.md | 0
.../viewer}/styles.ts | 2 +-
.../viewer}/template.ts | 0
.../testing/rich-text-viewer.pageobject.ts | 0
.../tests/rich-text-viewer-matrix.stories.ts | 12 +-
.../viewer}/tests/rich-text-viewer.spec.ts | 8 +-
.../viewer}/tests/rich-text-viewer.stories.ts | 4 +-
27 files changed, 1201 insertions(+), 589 deletions(-)
delete mode 100644 packages/nimble-components/src/rich-text-editor/index.ts
create mode 100644 packages/nimble-components/src/rich-text/editor/index.ts
rename packages/nimble-components/src/{rich-text-editor => rich-text/editor}/specs/README.md (96%)
rename packages/nimble-components/src/{rich-text-editor => rich-text/editor}/specs/spec-images/button-state.png (100%)
rename packages/nimble-components/src/{rich-text-editor => rich-text/editor}/specs/spec-images/editor-sample.png (100%)
rename packages/nimble-components/src/{rich-text-editor => rich-text/editor}/specs/spec-images/viewer-sample.png (100%)
rename packages/nimble-components/src/{rich-text-editor => rich-text/editor}/styles.ts (64%)
rename packages/nimble-components/src/{rich-text-editor => rich-text/editor}/template.ts (75%)
rename packages/nimble-components/src/{rich-text-editor => rich-text/editor}/testing/rich-text-editor.pageobject.ts (75%)
rename packages/nimble-components/src/{rich-text-editor => rich-text/editor}/testing/types.ts (100%)
rename packages/nimble-components/src/{rich-text-editor => rich-text/editor}/tests/rich-text-editor-matrix.stories.ts (54%)
rename packages/nimble-components/src/{rich-text-editor => rich-text/editor}/tests/rich-text-editor.spec.ts (86%)
rename packages/nimble-components/src/{rich-text-editor => rich-text/editor}/tests/rich-text-editor.stories.ts (71%)
rename packages/nimble-components/src/{rich-text-editor => rich-text/editor}/tests/types.spec.ts (100%)
rename packages/nimble-components/src/{rich-text-viewer/index.ts => rich-text/models/markdown-parser.ts} (55%)
create mode 100644 packages/nimble-components/src/rich-text/models/markdown-serializer.ts
create mode 100644 packages/nimble-components/src/rich-text/viewer/index.ts
rename packages/nimble-components/src/{rich-text-viewer => rich-text/viewer}/specs/README.md (100%)
rename packages/nimble-components/src/{rich-text-viewer => rich-text/viewer}/styles.ts (96%)
rename packages/nimble-components/src/{rich-text-viewer => rich-text/viewer}/template.ts (100%)
rename packages/nimble-components/src/{rich-text-viewer => rich-text/viewer}/testing/rich-text-viewer.pageobject.ts (100%)
rename packages/nimble-components/src/{rich-text-viewer => rich-text/viewer}/tests/rich-text-viewer-matrix.stories.ts (91%)
rename packages/nimble-components/src/{rich-text-viewer => rich-text/viewer}/tests/rich-text-viewer.spec.ts (99%)
rename packages/nimble-components/src/{rich-text-viewer => rich-text/viewer}/tests/rich-text-viewer.stories.ts (92%)
diff --git a/package-lock.json b/package-lock.json
index 17c6824c54..a7c8871191 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9477,9 +9477,9 @@
}
},
"node_modules/@tiptap/core": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-2.0.4.tgz",
- "integrity": "sha512-2YOMjRqoBGEP4YGgYpuPuBBJHMeqKOhLnS0WVwjVP84zOmMgZ7A8M6ILC9Xr7Q/qHZCvyBGWOSsI7+3HsEzzYQ==",
+ "version": "2.1.7",
+ "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-2.1.7.tgz",
+ "integrity": "sha512-1pqTwlTnwTKQSNQmmTWhs2lwdvd+hFFNFZnrRAfvZhQZA6qPmPmKMNTcYmK38Tn4axKth6mhBamzTJgMZFI7ng==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
@@ -9489,9 +9489,9 @@
}
},
"node_modules/@tiptap/extension-bold": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/@tiptap/extension-bold/-/extension-bold-2.0.4.tgz",
- "integrity": "sha512-CWSQy1uWkVsen8HUsqhm+oEIxJrCiCENABUbhaVcJL/MqhnP4Trrh1B6O00Yfoc0XToPRRibDaHMFs4A3MSO0g==",
+ "version": "2.1.7",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-bold/-/extension-bold-2.1.7.tgz",
+ "integrity": "sha512-GZV2D91WENkWd1W29vM4kyGWObcxOKQrY8MuCvTdxni1kobEc/LPZzQ1XiQmiNTvXTMcBz5ckLpezdjASV1dNg==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
@@ -9501,9 +9501,9 @@
}
},
"node_modules/@tiptap/extension-bullet-list": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/@tiptap/extension-bullet-list/-/extension-bullet-list-2.0.4.tgz",
- "integrity": "sha512-JSZKBVTaKSuLl5fR4EKE4dOINOrgeRHYA25Vj6cWjgdvpTw5ef7vcUdn9yP4JwTmLRI+VnnMlYL3rqigU3iZNg==",
+ "version": "2.1.7",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-bullet-list/-/extension-bullet-list-2.1.7.tgz",
+ "integrity": "sha512-BReix1wkGNH12DSWGnWPKNu4do92Avh98aLkRS1o1V1Y49/+YGMYtfBXB9obq40o0WqKvk4MoM+rhKbfEc44Gg==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
@@ -9513,9 +9513,9 @@
}
},
"node_modules/@tiptap/extension-document": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/@tiptap/extension-document/-/extension-document-2.0.4.tgz",
- "integrity": "sha512-mCj2fAhnNhIHttPSqfTPSSTGwClGaPYvhT56Ij/Pi4iCrWjPXzC4XnIkIHSS34qS2tJN4XJzr/z7lm3NeLkF1w==",
+ "version": "2.1.7",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-document/-/extension-document-2.1.7.tgz",
+ "integrity": "sha512-tZyoPPmvzti7PEnyulXomEtINd/Oi2S84uOt6gw7DTCnDq5bF5sn1IfN8Icqp9t4jDwyLXy2TL0Zg/sR0a2Ibg==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
@@ -9525,9 +9525,9 @@
}
},
"node_modules/@tiptap/extension-history": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/@tiptap/extension-history/-/extension-history-2.0.4.tgz",
- "integrity": "sha512-3GAUszn1xZx3vniHMiX9BSKmfvb5QOb0oSLXInN+hx80CgJDIHqIFuhx2dyV9I/HWpa0cTxaLWj64kfDzb1JVg==",
+ "version": "2.1.7",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-history/-/extension-history-2.1.7.tgz",
+ "integrity": "sha512-8SIEKSImrIkqJThym1bPD13sC4/76UrG+piQ30xKQU4B7zUFCbutvrwYuQHSRvaEt8BPdTv2LWIK+wBkIgbWVA==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
@@ -9538,9 +9538,9 @@
}
},
"node_modules/@tiptap/extension-italic": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/@tiptap/extension-italic/-/extension-italic-2.0.4.tgz",
- "integrity": "sha512-C/6+qs4Jh8xERRP0wcOopA1+emK8MOkBE4RQx5NbPnT2iCpERP0GlmHBFQIjaYPctZgKFHxsCfRnneS5Xe76+A==",
+ "version": "2.1.7",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-italic/-/extension-italic-2.1.7.tgz",
+ "integrity": "sha512-7e37f+OFqisdY19nWIthbSNHMJy4+4dec06rUICPrkiuFaADj5HjUQr0dyWpL/LkZh92Wf/rWgp4V/lEwon3jA==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
@@ -9550,9 +9550,9 @@
}
},
"node_modules/@tiptap/extension-list-item": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/@tiptap/extension-list-item/-/extension-list-item-2.0.4.tgz",
- "integrity": "sha512-tSkbLgRo1QMNDJttWs9FeRywkuy5T2HdLKKfUcUNzT3s0q5AqIJl7VyimsBL4A6MUfN1qQMZCMHB4pM9Mkluww==",
+ "version": "2.1.7",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-list-item/-/extension-list-item-2.1.7.tgz",
+ "integrity": "sha512-hd/E4qQopBXWa6kdFY19qFVgqj4fzdPgAnzdXJ2XW7bC6O2CusmHphRRZ5FBsuspYTN/6/fv0i0jK9rSGlsEyA==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
@@ -9562,9 +9562,9 @@
}
},
"node_modules/@tiptap/extension-ordered-list": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/@tiptap/extension-ordered-list/-/extension-ordered-list-2.0.4.tgz",
- "integrity": "sha512-Kfg+8k9p4iJCUKP/yIa18LfUpl9trURSMP/HX3/yQTz9Ul1vDrjxeFjSE5uWNvupcXRAM24js+aYrCmV7zpU+Q==",
+ "version": "2.1.7",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-ordered-list/-/extension-ordered-list-2.1.7.tgz",
+ "integrity": "sha512-3XIXqbZmYkNzF+8PQ2jcCOCj0lpC3y9HGM/+joPIunhiUiktrIgpbUDv2E1Gq5lJHYqthIeujniI2dB85tkwJQ==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
@@ -9574,9 +9574,9 @@
}
},
"node_modules/@tiptap/extension-paragraph": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/@tiptap/extension-paragraph/-/extension-paragraph-2.0.4.tgz",
- "integrity": "sha512-nDxpopi9WigVqpfi8nU3B0fWYB14EMvKIkutNZo8wJvKGTZufNI8hw66wupIx/jZH1gFxEa5dHerw6aSYuWjgQ==",
+ "version": "2.1.7",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-paragraph/-/extension-paragraph-2.1.7.tgz",
+ "integrity": "sha512-cLqX27hNrXrwZCKrIW8OC3rW2+MT8hhS37+cdqOxZo5hUqQ9EF/puwS0w8uUZ7B3awX9Jm1QZDMjjERLkcmobw==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
@@ -9585,10 +9585,23 @@
"@tiptap/core": "^2.0.0"
}
},
+ "node_modules/@tiptap/extension-placeholder": {
+ "version": "2.1.7",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-placeholder/-/extension-placeholder-2.1.7.tgz",
+ "integrity": "sha512-IiBoItYYNS7hb/zmPitw3w6Cylmp9qX+zW+QKe3lDkCNPeKxyQr86AnVLcQYOuXg62cLV9dp+4azZzHoz9SOcg==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/ueberdosis"
+ },
+ "peerDependencies": {
+ "@tiptap/core": "^2.0.0",
+ "@tiptap/pm": "^2.0.0"
+ }
+ },
"node_modules/@tiptap/extension-text": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/@tiptap/extension-text/-/extension-text-2.0.4.tgz",
- "integrity": "sha512-i8/VFlVZh7TkAI49KKX5JmC0tM8RGwyg5zUpozxYbLdCOv07AkJt+E1fLJty9mqH4Y5HJMNnyNxsuZ9Ol/ySRA==",
+ "version": "2.1.7",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-text/-/extension-text-2.1.7.tgz",
+ "integrity": "sha512-3xaMMMNydLgoS+o+yOvaZF04ui9spJwJZl8VyYgcJKVGGLGRlWHrireXN5/OqXG2jLb/jWqXVx5idppQjX+PMA==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
@@ -33325,16 +33338,17 @@
"@ni/nimble-tokens": "^6.3.0",
"@tanstack/table-core": "^8.9.3",
"@tanstack/virtual-core": "^3.0.0-beta.44",
- "@tiptap/core": "^2.0.4",
- "@tiptap/extension-bold": "^2.0.4",
- "@tiptap/extension-bullet-list": "^2.0.4",
- "@tiptap/extension-document": "^2.0.4",
- "@tiptap/extension-history": "^2.0.4",
- "@tiptap/extension-italic": "^2.0.4",
- "@tiptap/extension-list-item": "^2.0.4",
- "@tiptap/extension-ordered-list": "^2.0.4",
- "@tiptap/extension-paragraph": "^2.0.4",
- "@tiptap/extension-text": "^2.0.4",
+ "@tiptap/core": "^2.1.6",
+ "@tiptap/extension-bold": "^2.1.6",
+ "@tiptap/extension-bullet-list": "^2.1.6",
+ "@tiptap/extension-document": "^2.1.6",
+ "@tiptap/extension-history": "^2.1.6",
+ "@tiptap/extension-italic": "^2.1.6",
+ "@tiptap/extension-list-item": "^2.1.6",
+ "@tiptap/extension-ordered-list": "^2.1.6",
+ "@tiptap/extension-paragraph": "^2.1.6",
+ "@tiptap/extension-placeholder": "^2.1.6",
+ "@tiptap/extension-text": "^2.1.6",
"@types/d3-array": "^3.0.4",
"@types/d3-random": "^3.0.1",
"@types/d3-scale": "^4.0.2",
diff --git a/packages/nimble-components/package.json b/packages/nimble-components/package.json
index f7edd3b9bc..07c757fa6c 100644
--- a/packages/nimble-components/package.json
+++ b/packages/nimble-components/package.json
@@ -64,16 +64,17 @@
"@ni/nimble-tokens": "^6.3.0",
"@tanstack/table-core": "^8.9.3",
"@tanstack/virtual-core": "^3.0.0-beta.44",
- "@tiptap/core": "^2.0.4",
- "@tiptap/extension-bold": "^2.0.4",
- "@tiptap/extension-bullet-list": "^2.0.4",
- "@tiptap/extension-document": "^2.0.4",
- "@tiptap/extension-history": "^2.0.4",
- "@tiptap/extension-italic": "^2.0.4",
- "@tiptap/extension-list-item": "^2.0.4",
- "@tiptap/extension-ordered-list": "^2.0.4",
- "@tiptap/extension-paragraph": "^2.0.4",
- "@tiptap/extension-text": "^2.0.4",
+ "@tiptap/core": "^2.1.6",
+ "@tiptap/extension-bold": "^2.1.6",
+ "@tiptap/extension-bullet-list": "^2.1.6",
+ "@tiptap/extension-document": "^2.1.6",
+ "@tiptap/extension-history": "^2.1.6",
+ "@tiptap/extension-italic": "^2.1.6",
+ "@tiptap/extension-list-item": "^2.1.6",
+ "@tiptap/extension-ordered-list": "^2.1.6",
+ "@tiptap/extension-paragraph": "^2.1.6",
+ "@tiptap/extension-placeholder": "^2.1.6",
+ "@tiptap/extension-text": "^2.1.6",
"@types/d3-array": "^3.0.4",
"@types/d3-random": "^3.0.1",
"@types/d3-scale": "^4.0.2",
diff --git a/packages/nimble-components/src/all-components.ts b/packages/nimble-components/src/all-components.ts
index 4ed75dd127..db13e259ad 100644
--- a/packages/nimble-components/src/all-components.ts
+++ b/packages/nimble-components/src/all-components.ts
@@ -33,8 +33,8 @@ import './menu-item';
import './number-field';
import './radio';
import './radio-group';
-import './rich-text-editor';
-import './rich-text-viewer';
+import './rich-text/editor';
+import './rich-text/viewer';
import './select';
import './spinner';
import './switch';
diff --git a/packages/nimble-components/src/rich-text-editor/index.ts b/packages/nimble-components/src/rich-text-editor/index.ts
deleted file mode 100644
index b88609bf97..0000000000
--- a/packages/nimble-components/src/rich-text-editor/index.ts
+++ /dev/null
@@ -1,361 +0,0 @@
-import { observable } from '@microsoft/fast-element';
-import { DesignSystem, FoundationElement } from '@microsoft/fast-foundation';
-import { keyEnter, keySpace } from '@microsoft/fast-web-utilities';
-import { Editor } from '@tiptap/core';
-import {
- schema,
- defaultMarkdownParser,
- MarkdownParser,
- MarkdownSerializer,
- defaultMarkdownSerializer,
- MarkdownSerializerState
-} from 'prosemirror-markdown';
-import { DOMSerializer, Node } from 'prosemirror-model';
-import Bold from '@tiptap/extension-bold';
-import BulletList from '@tiptap/extension-bullet-list';
-import Document from '@tiptap/extension-document';
-import History from '@tiptap/extension-history';
-import Italic from '@tiptap/extension-italic';
-import ListItem from '@tiptap/extension-list-item';
-import OrderedList from '@tiptap/extension-ordered-list';
-import Paragraph from '@tiptap/extension-paragraph';
-import Text from '@tiptap/extension-text';
-import { template } from './template';
-import { styles } from './styles';
-import type { ToggleButton } from '../toggle-button';
-
-declare global {
- interface HTMLElementTagNameMap {
- 'nimble-rich-text-editor': RichTextEditor;
- }
-}
-
-/**
- * A nimble styled rich text editor
- */
-export class RichTextEditor extends FoundationElement {
- /**
- * @internal
- */
- @observable
- public boldButton!: ToggleButton;
-
- /**
- * @internal
- */
- @observable
- public italicsButton!: ToggleButton;
-
- /**
- * @internal
- */
- @observable
- public bulletListButton!: ToggleButton;
-
- /**
- * @internal
- */
- @observable
- public numberedListButton!: ToggleButton;
-
- /**
- * @internal
- */
- public editorContainer!: HTMLDivElement;
-
- private tiptapEditor!: Editor;
- private editor!: HTMLDivElement;
-
- private readonly markdownParser = this.initializeMarkdownParser();
- private readonly markdownSerializer = this.initializeMarkdownSerializer();
- private readonly domSerializer = DOMSerializer.fromSchema(schema);
- private readonly xmlSerializer = new XMLSerializer();
-
- public constructor() {
- super();
- this.initializeEditor();
- }
-
- /**
- * @internal
- */
- public override connectedCallback(): void {
- super.connectedCallback();
- if (!this.editor.isConnected) {
- this.editorContainer.append(this.editor);
- }
- this.bindEditorTransactionEvent();
- }
-
- /**
- * @internal
- */
- public override disconnectedCallback(): void {
- super.disconnectedCallback();
- this.unbindEditorTransactionEvent();
- }
-
- /**
- * Toggle the bold mark and focus back to the editor
- * @internal
- */
- public boldButtonClick(): void {
- this.tiptapEditor.chain().focus().toggleBold().run();
- }
-
- /**
- * Toggle the bold mark and focus back to the editor
- * @internal
- */
- public boldButtonKeyDown(event: KeyboardEvent): boolean {
- if (this.keyActivatesButton(event)) {
- this.tiptapEditor.chain().focus().toggleBold().run();
- return false;
- }
- return true;
- }
-
- /**
- * Toggle the italics mark and focus back to the editor
- * @internal
- */
- public italicsButtonClick(): void {
- this.tiptapEditor.chain().focus().toggleItalic().run();
- }
-
- /**
- * Toggle the italics mark and focus back to the editor
- * @internal
- */
- public italicsButtonKeyDown(event: KeyboardEvent): boolean {
- if (this.keyActivatesButton(event)) {
- this.tiptapEditor.chain().focus().toggleItalic().run();
- return false;
- }
- return true;
- }
-
- /**
- * Toggle the unordered list node and focus back to the editor
- * @internal
- */
- public bulletListButtonClick(): void {
- this.tiptapEditor.chain().focus().toggleBulletList().run();
- }
-
- /**
- * Toggle the unordered list node and focus back to the editor
- * @internal
- */
- public bulletListButtonKeyDown(event: KeyboardEvent): boolean {
- if (this.keyActivatesButton(event)) {
- this.tiptapEditor.chain().focus().toggleBulletList().run();
- return false;
- }
- return true;
- }
-
- /**
- * Toggle the ordered list node and focus back to the editor
- * @internal
- */
- public numberedListButtonClick(): void {
- this.tiptapEditor.chain().focus().toggleOrderedList().run();
- }
-
- /**
- * Toggle the ordered list node and focus back to the editor
- * @internal
- */
- public numberedListButtonKeyDown(event: KeyboardEvent): boolean {
- if (this.keyActivatesButton(event)) {
- this.tiptapEditor.chain().focus().toggleOrderedList().run();
- return false;
- }
- return true;
- }
-
- /**
- * This function load tip tap editor with provided markdown content by parsing into html
- * @public
- */
- public setMarkdown(markdown: string): void {
- const html = this.getHtmlContent(markdown);
- this.tiptapEditor.commands.setContent(html);
- }
-
- /**
- * This function returns markdown string by serializing tiptap editor document using prosemirror MarkdownSerializer
- * @public
- */
- public getMarkdown(): string {
- const markdownContent = this.markdownSerializer.serialize(
- this.tiptapEditor.state.doc
- );
- return markdownContent;
- }
-
- /**
- * @internal
- */
- public stopEventPropagation(event: Event): boolean {
- // Don't bubble the 'change' event from the toggle button because
- // all the formatting button has its own 'toggle' event through 'click' and 'keydown'.
- event.stopPropagation();
- return false;
- }
-
- /**
- * This function takes the Fragment from parseMarkdownToDOM function and return the serialized string using XMLSerializer
- */
- private getHtmlContent(markdown: string): string {
- const documentFragment = this.parseMarkdownToDOM(markdown);
- return this.xmlSerializer.serializeToString(documentFragment);
- }
-
- private initializeMarkdownParser(): MarkdownParser {
- /**
- * It configures the tokenizer of the default Markdown parser with the 'zero' preset.
- * The 'zero' preset is a configuration with no rules enabled by default to selectively enable specific rules.
- * https://github.com/markdown-it/markdown-it/blob/2b6cac25823af011ff3bc7628bc9b06e483c5a08/lib/presets/zero.js#L1
- *
- */
- const zeroTokenizerConfiguration = defaultMarkdownParser.tokenizer.configure('zero');
-
- // The detailed information of the supported rules were provided in the below CommonMark spec document.
- // https://spec.commonmark.org/0.30/
- const supportedTokenizerRules = zeroTokenizerConfiguration.enable([
- 'emphasis',
- 'list'
- ]);
-
- return new MarkdownParser(
- schema,
- supportedTokenizerRules,
- defaultMarkdownParser.tokens
- );
- }
-
- private initializeMarkdownSerializer(): MarkdownSerializer {
- /**
- * orderedList Node is getting 'order' attribute which it is not present in the
- * tip-tap orderedList Node and having start instead of order, Changed it to start (nodes.attrs.start)
- * Assigned updated node in place of orderedList node from defaultMarkdownSerializer
- * https://github.com/ProseMirror/prosemirror-markdown/blob/b7c1fd2fb74c7564bfe5428c7c8141ded7ebdd9f/src/to_markdown.ts#L94C2-L101C7
- */
- const orderedListNode = function orderedList(
- state: MarkdownSerializerState,
- node: Node
- ): void {
- const start = (node.attrs.start as number) || 1;
- const maxW = String(start + node.childCount - 1).length;
- const space = state.repeat(' ', maxW + 2);
- state.renderList(node, space, i => {
- const nStr = String(start + i);
- return `${state.repeat(' ', maxW - nStr.length) + nStr}. `;
- });
- };
-
- /**
- * Internally Tiptap editor creates it own schema ( Nodes AND Marks ) based on the extensions ( Here Starter Kit is used for Bold, italic, orderedList and
- * bulletList extensions) and defaultMarkdownSerializer uses schema from prosemirror-markdown to serialize the markdown.
- * So, there is variations in the nodes and marks name (Eg. 'ordered_list' in prosemirror-markdown schema whereas 'orderedList' in tip tap editor schema),
- * To fix up this reassigned the respective nodes and marks with tip-tap editor schema.
- */
- const nodes = {
- bulletList: defaultMarkdownSerializer.nodes.bullet_list!,
- listItem: defaultMarkdownSerializer.nodes.list_item!,
- orderedList: orderedListNode,
- doc: defaultMarkdownSerializer.nodes.doc!,
- paragraph: defaultMarkdownSerializer.nodes.paragraph!,
- text: defaultMarkdownSerializer.nodes.text!
- };
- const marks = {
- italic: defaultMarkdownSerializer.marks.em!,
- bold: defaultMarkdownSerializer.marks.strong!
- };
- return new MarkdownSerializer(nodes, marks);
- }
-
- private parseMarkdownToDOM(value: string): HTMLElement | DocumentFragment {
- const parsedMarkdownContent = this.markdownParser.parse(value);
- if (parsedMarkdownContent === null) {
- return document.createDocumentFragment();
- }
-
- return this.domSerializer.serializeFragment(
- parsedMarkdownContent.content
- );
- }
-
- private initializeEditor(): void {
- // Create div from the constructor because the TipTap editor requires its host element before the template is instantiated.
- this.editor = document.createElement('div');
- this.editor.className = 'editor';
- this.editor.setAttribute('aria-multiline', 'true');
- this.editor.setAttribute('role', 'textbox');
-
- /**
- * For more information on the extensions for the supported formatting options, refer to the links below.
- * Tiptap marks: https://tiptap.dev/api/marks
- * Tiptap nodes: https://tiptap.dev/api/nodes
- */
- this.tiptapEditor = new Editor({
- element: this.editor,
- extensions: [
- Document,
- Paragraph,
- Text,
- BulletList,
- OrderedList,
- ListItem,
- Bold,
- Italic,
- History
- ]
- });
- }
-
- /**
- * Binding the "transaction" event to the editor allows continuous monitoring the events and updating the button state in response to
- * various actions such as mouse events, keyboard events, changes in the editor content etc,.
- * https://tiptap.dev/api/events#transaction
- */
- private bindEditorTransactionEvent(): void {
- this.tiptapEditor.on('transaction', () => {
- this.updateEditorButtonsState();
- });
- }
-
- private unbindEditorTransactionEvent(): void {
- this.tiptapEditor.off('transaction');
- }
-
- private updateEditorButtonsState(): void {
- this.boldButton.checked = this.tiptapEditor.isActive('bold');
- this.italicsButton.checked = this.tiptapEditor.isActive('italic');
- this.bulletListButton.checked = this.tiptapEditor.isActive('bulletList');
- this.numberedListButton.checked = this.tiptapEditor.isActive('orderedList');
- }
-
- private keyActivatesButton(event: KeyboardEvent): boolean {
- switch (event.key) {
- case keySpace:
- case keyEnter:
- return true;
- default:
- return false;
- }
- }
-}
-
-const nimbleRichTextEditor = RichTextEditor.compose({
- baseName: 'rich-text-editor',
- template,
- styles
-});
-
-DesignSystem.getOrCreate()
- .withPrefix('nimble')
- .register(nimbleRichTextEditor());
-export const richTextEditorTag = DesignSystem.tagFor(RichTextEditor);
diff --git a/packages/nimble-components/src/rich-text/editor/index.ts b/packages/nimble-components/src/rich-text/editor/index.ts
new file mode 100644
index 0000000000..173e7a9cf7
--- /dev/null
+++ b/packages/nimble-components/src/rich-text/editor/index.ts
@@ -0,0 +1,481 @@
+import { observable, attr, DOM } from '@microsoft/fast-element';
+import {
+ applyMixins,
+ ARIAGlobalStatesAndProperties,
+ DesignSystem,
+ FoundationElement
+} from '@microsoft/fast-foundation';
+import { keyEnter, keySpace } from '@microsoft/fast-web-utilities';
+import { Editor, AnyExtension, Extension } from '@tiptap/core';
+import Bold from '@tiptap/extension-bold';
+import BulletList from '@tiptap/extension-bullet-list';
+import Document from '@tiptap/extension-document';
+import History from '@tiptap/extension-history';
+import Italic from '@tiptap/extension-italic';
+import ListItem from '@tiptap/extension-list-item';
+import OrderedList from '@tiptap/extension-ordered-list';
+import Paragraph from '@tiptap/extension-paragraph';
+import Placeholder from '@tiptap/extension-placeholder';
+import type { PlaceholderOptions } from '@tiptap/extension-placeholder';
+import Text from '@tiptap/extension-text';
+import { template } from './template';
+import { styles } from './styles';
+import type { ToggleButton } from '../../toggle-button';
+import type { ErrorPattern } from '../../patterns/error/types';
+import { RichTextMarkdownParser } from '../models/markdown-parser';
+import { richTextMarkdownSerializer } from '../models/markdown-serializer';
+
+declare global {
+ interface HTMLElementTagNameMap {
+ 'nimble-rich-text-editor': RichTextEditor;
+ }
+}
+
+/**
+ * A nimble styled rich text editor
+ */
+export class RichTextEditor extends FoundationElement implements ErrorPattern {
+ /**
+ * @internal
+ */
+ public editor = this.createEditor();
+
+ /**
+ * @internal
+ */
+ public tiptapEditor = this.createTiptapEditor();
+
+ /**
+ * Whether to disable user from editing and interacting with toolbar buttons
+ *
+ * @public
+ * HTML Attribute: disabled
+ */
+ @attr({ mode: 'boolean' })
+ public disabled = false;
+
+ /**
+ * Whether to hide the footer of the rich text editor
+ *
+ * @public
+ * HTML Attribute: footer-hidden
+ */
+ @attr({ attribute: 'footer-hidden', mode: 'boolean' })
+ public footerHidden = false;
+
+ /**
+ * Whether to display the error state.
+ *
+ * @public
+ * HTML Attribute: error-visible
+ */
+ @attr({ attribute: 'error-visible', mode: 'boolean' })
+ public errorVisible = false;
+
+ /**
+ * A message explaining why the value is invalid.
+ *
+ * @public
+ * HTML Attribute: error-text
+ */
+ @attr({ attribute: 'error-text' })
+ public errorText?: string;
+
+ /**
+ * @public
+ * HTML Attribute: placeholder
+ */
+ @attr
+ public placeholder?: string;
+
+ /**
+ * True if the editor is empty or contains only whitespace, false otherwise.
+ *
+ * @public
+ */
+ public get empty(): boolean {
+ // Tiptap [isEmpty](https://tiptap.dev/api/editor#is-empty) returns false even if the editor has only whitespace.
+ // However, the expectation is to return true if the editor is empty or contains only whitespace.
+ // Hence, by retrieving the current text content using Tiptap state docs and then trimming the string to determine whether it is empty or not.
+ return this.tiptapEditor.state.doc.textContent.trim().length === 0;
+ }
+
+ /**
+ * @internal
+ */
+ @observable
+ public boldButton!: ToggleButton;
+
+ /**
+ * @internal
+ */
+ @observable
+ public italicsButton!: ToggleButton;
+
+ /**
+ * @internal
+ */
+ @observable
+ public bulletListButton!: ToggleButton;
+
+ /**
+ * @internal
+ */
+ @observable
+ public numberedListButton!: ToggleButton;
+
+ /**
+ * The width of the vertical scrollbar, if displayed.
+ * @internal
+ */
+ @observable
+ public scrollbarWidth = -1;
+
+ /**
+ * @internal
+ */
+ public editorContainer!: HTMLDivElement;
+
+ private resizeObserver?: ResizeObserver;
+ private updateScrollbarWidthQueued = false;
+
+ private readonly markdownParser = new RichTextMarkdownParser();
+ private readonly markdownSerializer = richTextMarkdownSerializer();
+ private readonly xmlSerializer = new XMLSerializer();
+
+ /**
+ * @internal
+ */
+ public override connectedCallback(): void {
+ super.connectedCallback();
+ if (!this.editor.isConnected) {
+ this.editorContainer.append(this.editor);
+ }
+ this.bindEditorTransactionEvent();
+ this.bindEditorUpdateEvent();
+ this.stopNativeInputEventPropagation();
+ this.resizeObserver = new ResizeObserver(() => this.onResize());
+ this.resizeObserver.observe(this);
+ }
+
+ /**
+ * @internal
+ */
+ public override disconnectedCallback(): void {
+ super.disconnectedCallback();
+ this.unbindEditorTransactionEvent();
+ this.unbindEditorUpdateEvent();
+ this.unbindNativeInputEvent();
+ this.resizeObserver?.disconnect();
+ }
+
+ /**
+ * @internal
+ */
+ public disabledChanged(): void {
+ this.tiptapEditor.setEditable(!this.disabled);
+ this.setEditorTabIndex();
+ this.editor.setAttribute(
+ 'aria-disabled',
+ this.disabled ? 'true' : 'false'
+ );
+ }
+
+ /**
+ * Update the placeholder text and view of the editor.
+ * @internal
+ */
+ public placeholderChanged(): void {
+ const placeholderExtension = this.getTipTapExtension(
+ 'placeholder'
+ ) as Extension;
+ placeholderExtension.options.placeholder = this.placeholder ?? '';
+ this.tiptapEditor.view.dispatch(this.tiptapEditor.state.tr);
+
+ this.queueUpdateScrollbarWidth();
+ }
+
+ /**
+ * @internal
+ */
+ public ariaLabelChanged(): void {
+ if (this.ariaLabel !== null && this.ariaLabel !== undefined) {
+ this.editor.setAttribute('aria-label', this.ariaLabel);
+ } else {
+ this.editor.removeAttribute('aria-label');
+ }
+ }
+
+ /**
+ * Toggle the bold mark and focus back to the editor
+ * @internal
+ */
+ public boldButtonClick(): void {
+ this.tiptapEditor.chain().focus().toggleBold().run();
+ }
+
+ /**
+ * Toggle the bold mark and focus back to the editor
+ * @internal
+ */
+ public boldButtonKeyDown(event: KeyboardEvent): boolean {
+ if (this.keyActivatesButton(event)) {
+ this.tiptapEditor.chain().focus().toggleBold().run();
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Toggle the italics mark and focus back to the editor
+ * @internal
+ */
+ public italicsButtonClick(): void {
+ this.tiptapEditor.chain().focus().toggleItalic().run();
+ }
+
+ /**
+ * Toggle the italics mark and focus back to the editor
+ * @internal
+ */
+ public italicsButtonKeyDown(event: KeyboardEvent): boolean {
+ if (this.keyActivatesButton(event)) {
+ this.tiptapEditor.chain().focus().toggleItalic().run();
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Toggle the unordered list node and focus back to the editor
+ * @internal
+ */
+ public bulletListButtonClick(): void {
+ this.tiptapEditor.chain().focus().toggleBulletList().run();
+ }
+
+ /**
+ * Toggle the unordered list node and focus back to the editor
+ * @internal
+ */
+ public bulletListButtonKeyDown(event: KeyboardEvent): boolean {
+ if (this.keyActivatesButton(event)) {
+ this.tiptapEditor.chain().focus().toggleBulletList().run();
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Toggle the ordered list node and focus back to the editor
+ * @internal
+ */
+ public numberedListButtonClick(): void {
+ this.tiptapEditor.chain().focus().toggleOrderedList().run();
+ }
+
+ /**
+ * Toggle the ordered list node and focus back to the editor
+ * @internal
+ */
+ public numberedListButtonKeyDown(event: KeyboardEvent): boolean {
+ if (this.keyActivatesButton(event)) {
+ this.tiptapEditor.chain().focus().toggleOrderedList().run();
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * This function load tip tap editor with provided markdown content by parsing into html
+ * @public
+ */
+ public setMarkdown(markdown: string): void {
+ const html = this.getHtmlContent(markdown);
+ this.tiptapEditor.commands.setContent(html);
+ }
+
+ /**
+ * This function returns markdown string by serializing tiptap editor document using prosemirror MarkdownSerializer
+ * @public
+ */
+ public getMarkdown(): string {
+ const markdownContent = this.markdownSerializer.serialize(
+ this.tiptapEditor.state.doc
+ );
+ return markdownContent;
+ }
+
+ /**
+ * @internal
+ */
+ public stopEventPropagation(event: Event): boolean {
+ // Don't bubble the 'change' event from the toggle button because
+ // all the formatting button has its own 'toggle' event through 'click' and 'keydown'.
+ event.stopPropagation();
+ return false;
+ }
+
+ private createEditor(): HTMLDivElement {
+ const editor = document.createElement('div');
+ editor.className = 'editor';
+ editor.setAttribute('aria-multiline', 'true');
+ editor.setAttribute('role', 'textbox');
+ editor.setAttribute('aria-disabled', 'false');
+ return editor;
+ }
+
+ private createTiptapEditor(): Editor {
+ /**
+ * For more information on the extensions for the supported formatting options, refer to the links below.
+ * Tiptap marks: https://tiptap.dev/api/marks
+ * Tiptap nodes: https://tiptap.dev/api/nodes
+ */
+ return new Editor({
+ element: this.editor,
+ extensions: [
+ Document,
+ Paragraph,
+ Text,
+ BulletList,
+ OrderedList,
+ ListItem,
+ Bold,
+ Italic,
+ History,
+ Placeholder.configure({
+ placeholder: '',
+ showOnlyWhenEditable: false
+ })
+ ]
+ });
+ }
+
+ /**
+ * This function takes the Fragment from parseMarkdownToDOM function and return the serialized string using XMLSerializer
+ */
+ private getHtmlContent(markdown: string): string {
+ const documentFragment = this.markdownParser.parseMarkdownToDOM(markdown);
+ return this.xmlSerializer.serializeToString(documentFragment);
+ }
+
+ /**
+ * Binding the "transaction" event to the editor allows continuous monitoring the events and updating the button state in response to
+ * various actions such as mouse events, keyboard events, changes in the editor content etc,.
+ * https://tiptap.dev/api/events#transaction
+ */
+ private bindEditorTransactionEvent(): void {
+ this.tiptapEditor.on('transaction', () => {
+ this.updateEditorButtonsState();
+ });
+ }
+
+ private unbindEditorTransactionEvent(): void {
+ this.tiptapEditor.off('transaction');
+ }
+
+ private updateEditorButtonsState(): void {
+ this.boldButton.checked = this.tiptapEditor.isActive('bold');
+ this.italicsButton.checked = this.tiptapEditor.isActive('italic');
+ this.bulletListButton.checked = this.tiptapEditor.isActive('bulletList');
+ this.numberedListButton.checked = this.tiptapEditor.isActive('orderedList');
+ }
+
+ private keyActivatesButton(event: KeyboardEvent): boolean {
+ switch (event.key) {
+ case keySpace:
+ case keyEnter:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ private unbindEditorUpdateEvent(): void {
+ this.tiptapEditor.off('update');
+ }
+
+ /**
+ * input event is fired when there is a change in the content of the editor.
+ *
+ * https://tiptap.dev/api/events#update
+ */
+ private bindEditorUpdateEvent(): void {
+ this.tiptapEditor.on('update', () => {
+ this.$emit('input');
+ this.queueUpdateScrollbarWidth();
+ });
+ }
+
+ /**
+ * Stopping the native input event propagation emitted by the contenteditable element in the Tiptap
+ * since there is an issue (linked below) in ProseMirror where selecting the text and removing it
+ * does not trigger the native HTMLElement input event. So using the "update" event emitted by the
+ * Tiptap to capture it as an "input" customEvent in the rich text editor.
+ *
+ * Prose Mirror issue: https://discuss.prosemirror.net/t/how-to-handle-select-backspace-delete-cut-type-kind-of-events-handletextinput-or-handledomevents-input-doesnt-help/4844
+ */
+ private stopNativeInputEventPropagation(): void {
+ this.tiptapEditor.view.dom.addEventListener('input', event => {
+ event.stopPropagation();
+ });
+ }
+
+ private unbindNativeInputEvent(): void {
+ this.tiptapEditor.view.dom.removeEventListener('input', () => {});
+ }
+
+ private queueUpdateScrollbarWidth(): void {
+ if (!this.$fastController.isConnected) {
+ return;
+ }
+ if (!this.updateScrollbarWidthQueued) {
+ this.updateScrollbarWidthQueued = true;
+ DOM.queueUpdate(() => this.updateScrollbarWidth());
+ }
+ }
+
+ private updateScrollbarWidth(): void {
+ this.updateScrollbarWidthQueued = false;
+ this.scrollbarWidth = this.tiptapEditor.view.dom.offsetWidth
+ - this.tiptapEditor.view.dom.clientWidth;
+ }
+
+ private onResize(): void {
+ this.scrollbarWidth = this.tiptapEditor.view.dom.offsetWidth
+ - this.tiptapEditor.view.dom.clientWidth;
+ }
+
+ private getTipTapExtension(
+ extensionName: string
+ ): AnyExtension | undefined {
+ return this.tiptapEditor.extensionManager.extensions.find(
+ extension => extension.name === extensionName
+ );
+ }
+
+ private setEditorTabIndex(): void {
+ this.tiptapEditor.setOptions({
+ editorProps: {
+ attributes: {
+ tabindex: this.disabled ? '-1' : '0'
+ }
+ }
+ });
+ }
+}
+
+// eslint-disable-next-line @typescript-eslint/no-empty-interface
+export interface RichTextEditor extends ARIAGlobalStatesAndProperties {}
+applyMixins(RichTextEditor, ARIAGlobalStatesAndProperties);
+
+const nimbleRichTextEditor = RichTextEditor.compose({
+ baseName: 'rich-text-editor',
+ template,
+ styles
+});
+
+DesignSystem.getOrCreate()
+ .withPrefix('nimble')
+ .register(nimbleRichTextEditor());
+export const richTextEditorTag = DesignSystem.tagFor(RichTextEditor);
diff --git a/packages/nimble-components/src/rich-text-editor/specs/README.md b/packages/nimble-components/src/rich-text/editor/specs/README.md
similarity index 96%
rename from packages/nimble-components/src/rich-text-editor/specs/README.md
rename to packages/nimble-components/src/rich-text/editor/specs/README.md
index d5928ee243..1157db3c4b 100644
--- a/packages/nimble-components/src/rich-text-editor/specs/README.md
+++ b/packages/nimble-components/src/rich-text/editor/specs/README.md
@@ -102,8 +102,8 @@ Example usage of the `nimble-rich-text-editor` in the application layer is as fo
_Props/Attrs_
-- `empty` - is a read-only property that indicates whether the editor is empty or not. This will be achieved through Tiptap's
- [isEmpty](https://tiptap.dev/api/editor#is-empty) API. The component and the Angular directive will have a getter method
+- `empty` - is a read-only property that indicates whether the editor is empty or not. This will be achieved by retrieving the current text
+ content from the editor and calculating its length. The component and the Angular directive will have a getter method
that can be used to bind it in the Angular application.
- `fit-to-content` - is a boolean attribute allows the text area to expand vertically to fit the content.
- `placeholder` - is a string attribute to include a placeholder text for the editor when it is empty. This text is passed as plain text (not markdown)
@@ -150,6 +150,14 @@ problematic when attempting to clear the editor's content by setting the markdow
empty and hasn't undergone processing. To overcome this issue, utilizing `methods` could offer a potential solution, allowing the content to be set regardless of whether it has
changed from its previous value.
+_empty_
+
+We considered utilizing Tiptap's [isEmpty](https://tiptap.dev/api/editor#is-empty) API to determine whether the editor is empty. However, this API
+does not return true if the editor only consists of whitespace. In the context of the comments feature, this property is exposed to find out the
+editor's empty state, even when it contains only whitespace. This is necessary because the Backend service for comments does not permit the
+creation of comments comprised of just whitespace. Consequently, by using this property, we should disable the `OK` button when the editor is
+empty. To achieve this, we retrieve the current text content value, trim the string, and return true if its length is zero.
+
_Events_
- `input` - event emitted when there is a change in the editor. This can be achieved through Tiptap's [update event](https://tiptap.dev/api/events#update).
diff --git a/packages/nimble-components/src/rich-text-editor/specs/spec-images/button-state.png b/packages/nimble-components/src/rich-text/editor/specs/spec-images/button-state.png
similarity index 100%
rename from packages/nimble-components/src/rich-text-editor/specs/spec-images/button-state.png
rename to packages/nimble-components/src/rich-text/editor/specs/spec-images/button-state.png
diff --git a/packages/nimble-components/src/rich-text-editor/specs/spec-images/editor-sample.png b/packages/nimble-components/src/rich-text/editor/specs/spec-images/editor-sample.png
similarity index 100%
rename from packages/nimble-components/src/rich-text-editor/specs/spec-images/editor-sample.png
rename to packages/nimble-components/src/rich-text/editor/specs/spec-images/editor-sample.png
diff --git a/packages/nimble-components/src/rich-text-editor/specs/spec-images/viewer-sample.png b/packages/nimble-components/src/rich-text/editor/specs/spec-images/viewer-sample.png
similarity index 100%
rename from packages/nimble-components/src/rich-text-editor/specs/spec-images/viewer-sample.png
rename to packages/nimble-components/src/rich-text/editor/specs/spec-images/viewer-sample.png
diff --git a/packages/nimble-components/src/rich-text-editor/styles.ts b/packages/nimble-components/src/rich-text/editor/styles.ts
similarity index 64%
rename from packages/nimble-components/src/rich-text-editor/styles.ts
rename to packages/nimble-components/src/rich-text/editor/styles.ts
index e32148e60e..2af9ce8127 100644
--- a/packages/nimble-components/src/rich-text-editor/styles.ts
+++ b/packages/nimble-components/src/rich-text/editor/styles.ts
@@ -1,17 +1,24 @@
import { css } from '@microsoft/fast-element';
import { display } from '@microsoft/fast-foundation';
import {
+ bodyDisabledFontColor,
bodyFont,
bodyFontColor,
borderHoverColor,
borderRgbPartialColor,
borderWidth,
+ controlLabelFontColor,
+ controlLabelDisabledFontColor,
+ failColor,
+ iconSize,
smallDelay,
standardPadding
-} from '../theme-provider/design-tokens';
+} from '../../theme-provider/design-tokens';
+import { styles as errorStyles } from '../../patterns/error/styles';
export const styles = css`
${display('inline-flex')}
+ ${errorStyles}
:host {
font: ${bodyFont};
@@ -21,6 +28,10 @@ export const styles = css`
--ni-private-rich-text-editor-hover-indicator-width: calc(
${borderWidth} + 1px
);
+ ${
+ /** Initial height of rich text editor with one line space when the footer is visible. */ ''
+ }
+ height: 82px;
--ni-private-rich-text-editor-footer-section-height: 40px;
${
/** Minimum width is added to accommodate all the possible buttons in the toolbar and to support the mobile width. */ ''
@@ -29,6 +40,7 @@ export const styles = css`
}
.container {
+ box-sizing: border-box;
display: flex;
flex-direction: column;
position: relative;
@@ -60,38 +72,56 @@ export const styles = css`
}
}
+ :host([disabled]) .container {
+ color: ${bodyDisabledFontColor};
+ border: ${borderWidth} solid rgba(${borderRgbPartialColor}, 0.1);
+ }
+
+ :host([error-visible]) .container {
+ border-bottom-color: ${failColor};
+ }
+
:host(:hover) .container::after {
- width: 100%;
+ width: calc(100% + 2 * ${borderWidth});
+ }
+
+ :host([disabled]:hover) .container::after {
+ width: 0px;
+ }
+
+ :host([error-visible]) .container::after {
+ border-bottom-color: ${failColor};
+ }
+
+ .editor-container {
+ display: contents;
}
.editor {
+ display: flex;
+ flex-direction: column;
border: ${borderWidth} solid transparent;
border-radius: 0px;
- height: calc(
- 100% - var(--ni-private-rich-text-editor-footer-section-height)
- );
- overflow: auto;
+ flex: 1;
+ overflow: hidden;
}
- .editor-container {
- display: contents;
+ :host([footer-hidden]) .editor {
+ height: 100%;
}
.ProseMirror {
- ${
- /**
- * Min height represents the one line space for the initial view and max height is referred from the visual design.
- * However, max height will be `fit-content` when the `fit-to-content` attribute for the editor component is implemented.
- */ ''
- }
- min-height: 32px;
- max-height: 132px;
+ overflow: auto;
height: 100%;
- border: ${borderWidth} solid transparent;
+ border: 0px;
border-radius: 0px;
background-color: transparent;
font: inherit;
padding: 8px;
+ ${
+ /* This padding ensures that showing/hiding the error icon doesn't affect text layout */ ''
+ }
+ padding-right: calc(${iconSize});
box-sizing: border-box;
position: relative;
color: inherit;
@@ -139,15 +169,39 @@ export const styles = css`
margin-block: 0;
}
+ ${
+ /**
+ * Styles provided by Tiptap are necessary to display the placeholder value when the editor is empty.
+ * Tiptap doc reference: https://tiptap.dev/api/extensions/placeholder#additional-setup
+ */ ''
+ }
+ .ProseMirror p.is-editor-empty:first-child::before {
+ color: ${controlLabelFontColor};
+ content: attr(data-placeholder);
+ float: left;
+ height: 0;
+ pointer-events: none;
+ word-break: break-word;
+ }
+
+ :host([disabled]) .ProseMirror p.is-editor-empty:first-child::before {
+ color: ${controlLabelDisabledFontColor};
+ }
+
.footer-section {
display: flex;
justify-content: space-between;
+ flex-shrink: 0;
border: ${borderWidth} solid transparent;
border-top-color: rgba(${borderRgbPartialColor}, 0.1);
height: var(--ni-private-rich-text-editor-footer-section-height);
overflow: hidden;
}
+ :host([footer-hidden]) .footer-section {
+ display: none;
+ }
+
nimble-toolbar::part(positioning-region) {
background: transparent;
padding-right: 8px;
@@ -164,4 +218,15 @@ export const styles = css`
gap: ${standardPadding};
place-items: center;
}
+
+ :host([error-visible]) .error-icon {
+ display: none;
+ }
+
+ :host([error-visible]) .error-icon.scrollbar-width-calculated {
+ display: inline-flex;
+ position: absolute;
+ top: calc(${standardPadding} / 2);
+ right: var(--ni-private-rich-text-editor-scrollbar-width);
+ }
`;
diff --git a/packages/nimble-components/src/rich-text-editor/template.ts b/packages/nimble-components/src/rich-text/editor/template.ts
similarity index 75%
rename from packages/nimble-components/src/rich-text-editor/template.ts
rename to packages/nimble-components/src/rich-text/editor/template.ts
index e8db67808a..f171ade200 100644
--- a/packages/nimble-components/src/rich-text-editor/template.ts
+++ b/packages/nimble-components/src/rich-text/editor/template.ts
@@ -1,11 +1,13 @@
import { html, ref } from '@microsoft/fast-element';
import type { RichTextEditor } from '.';
-import { toolbarTag } from '../toolbar';
-import { toggleButtonTag } from '../toggle-button';
-import { iconBoldBTag } from '../icons/bold-b';
-import { iconItalicITag } from '../icons/italic-i';
-import { iconListTag } from '../icons/list';
-import { iconNumberListTag } from '../icons/number-list';
+import { toolbarTag } from '../../toolbar';
+import { toggleButtonTag } from '../../toggle-button';
+import { iconBoldBTag } from '../../icons/bold-b';
+import { iconItalicITag } from '../../icons/italic-i';
+import { iconListTag } from '../../icons/list';
+import { iconNumberListTag } from '../../icons/number-list';
+import { errorTextTemplate } from '../../patterns/error/template';
+import { iconExclamationMarkTag } from '../../icons/exclamation-mark';
// prettier-ignore
export const template = html`
@@ -13,12 +15,18 @@ export const template = html`
-
`;
diff --git a/packages/nimble-components/src/rich-text-editor/testing/rich-text-editor.pageobject.ts b/packages/nimble-components/src/rich-text/editor/testing/rich-text-editor.pageobject.ts
similarity index 75%
rename from packages/nimble-components/src/rich-text-editor/testing/rich-text-editor.pageobject.ts
rename to packages/nimble-components/src/rich-text/editor/testing/rich-text-editor.pageobject.ts
index 2ad02b8ee3..e81c375ab6 100644
--- a/packages/nimble-components/src/rich-text-editor/testing/rich-text-editor.pageobject.ts
+++ b/packages/nimble-components/src/rich-text/editor/testing/rich-text-editor.pageobject.ts
@@ -1,7 +1,7 @@
import { keySpace, keyEnter, keyTab } from '@microsoft/fast-web-utilities';
import type { RichTextEditor } from '..';
-import { waitForUpdatesAsync } from '../../testing/async-helpers';
-import type { ToggleButton } from '../../toggle-button';
+import { waitForUpdatesAsync } from '../../../testing/async-helpers';
+import type { ToggleButton } from '../../../toggle-button';
import type { ToolbarButton } from './types';
/**
@@ -72,38 +72,22 @@ export class RichTextEditorPageObject {
await waitForUpdatesAsync();
}
- /**
- * To click a formatting button in the footer section, pass its position value as an index (starting from '0')
- * @param button can be imported from an enum for each button using the `ButtonIndex`.
- */
public async clickFooterButton(button: ToolbarButton): Promise {
const toggleButton = this.getFormattingButton(button);
toggleButton!.click();
await waitForUpdatesAsync();
}
- /**
- * To retrieve the checked state of the button, provide its position value as an index (starting from '0')
- * @param button can be imported from an enum for each button using the `ButtonIndex`.
- */
public getButtonCheckedState(button: ToolbarButton): boolean {
const toggleButton = this.getFormattingButton(button);
return toggleButton!.checked;
}
- /**
- * To retrieve the tab index of the button, provide its position value as an index (starting from '0')
- * @param button can be imported from an enum for each button using the `ButtonIndex`.
- */
public getButtonTabIndex(button: ToolbarButton): number {
const toggleButton = this.getFormattingButton(button);
return toggleButton!.tabIndex;
}
- /**
- * To trigger a space key press for the button, provide its position value as an index (starting from '0')
- * @param button can be imported from an enum for each button using the `ButtonIndex`.
- */
public spaceKeyActivatesButton(button: ToolbarButton): void {
const toggleButton = this.getFormattingButton(button)!;
const event = new KeyboardEvent('keypress', {
@@ -112,10 +96,6 @@ export class RichTextEditorPageObject {
toggleButton.control.dispatchEvent(event);
}
- /**
- * To trigger a enter key press for the button, provide its position value as an index (starting from '0')
- * @param button can be imported from an enum for each button using the `ButtonIndex`.
- */
public enterKeyActivatesButton(button: ToolbarButton): void {
const toggleButton = this.getFormattingButton(button)!;
const event = new KeyboardEvent('keypress', {
@@ -156,10 +136,53 @@ export class RichTextEditorPageObject {
.map(el => el.textContent || '');
}
+ public getEditorTabIndex(): string {
+ return this.getTiptapEditor()?.getAttribute('tabindex') ?? '';
+ }
+
+ public async setFooterHidden(footerHidden: boolean): Promise {
+ if (footerHidden) {
+ this.richTextEditorElement.setAttribute('footer-hidden', '');
+ } else {
+ this.richTextEditorElement.removeAttribute('footer-hidden');
+ }
+ await waitForUpdatesAsync();
+ }
+
+ public isFooterHidden(): boolean {
+ const footerSection = this.getFooter()!;
+ return window.getComputedStyle(footerSection).display === 'none';
+ }
+
+ public async setDisabled(disabled: boolean): Promise {
+ if (disabled) {
+ this.richTextEditorElement.setAttribute('disabled', '');
+ } else {
+ this.richTextEditorElement.removeAttribute('disabled');
+ }
+ await waitForUpdatesAsync();
+ }
+
+ public isButtonDisabled(button: ToolbarButton): boolean {
+ const toggleButton = this.getFormattingButton(button)!;
+ return toggleButton.hasAttribute('disabled');
+ }
+
+ public getPlaceholderValue(): string {
+ const editor = this.getTiptapEditor()!;
+ return editor.firstElementChild?.getAttribute('data-placeholder') ?? '';
+ }
+
private getEditorSection(): Element | null | undefined {
return this.richTextEditorElement.shadowRoot?.querySelector('.editor');
}
+ private getFooter(): Element | null | undefined {
+ return this.richTextEditorElement.shadowRoot!.querySelector(
+ '.footer-section'
+ );
+ }
+
private getTiptapEditor(): Element | null | undefined {
return this.richTextEditorElement.shadowRoot?.querySelector(
'.ProseMirror'
@@ -167,11 +190,11 @@ export class RichTextEditorPageObject {
}
private getFormattingButton(
- index: ToolbarButton
+ button: ToolbarButton
): ToggleButton | null | undefined {
const buttons: NodeListOf = this.richTextEditorElement.shadowRoot!.querySelectorAll(
'nimble-toggle-button'
);
- return buttons[index];
+ return buttons[button];
}
}
diff --git a/packages/nimble-components/src/rich-text-editor/testing/types.ts b/packages/nimble-components/src/rich-text/editor/testing/types.ts
similarity index 100%
rename from packages/nimble-components/src/rich-text-editor/testing/types.ts
rename to packages/nimble-components/src/rich-text/editor/testing/types.ts
diff --git a/packages/nimble-components/src/rich-text-editor/tests/rich-text-editor-matrix.stories.ts b/packages/nimble-components/src/rich-text/editor/tests/rich-text-editor-matrix.stories.ts
similarity index 54%
rename from packages/nimble-components/src/rich-text-editor/tests/rich-text-editor-matrix.stories.ts
rename to packages/nimble-components/src/rich-text/editor/tests/rich-text-editor-matrix.stories.ts
index a88a07e49a..42223413be 100644
--- a/packages/nimble-components/src/rich-text-editor/tests/rich-text-editor-matrix.stories.ts
+++ b/packages/nimble-components/src/rich-text/editor/tests/rich-text-editor-matrix.stories.ts
@@ -3,19 +3,25 @@ import { html, ViewTemplate } from '@microsoft/fast-element';
import {
createMatrixThemeStory,
createStory
-} from '../../utilities/tests/storybook';
+} from '../../../utilities/tests/storybook';
import {
createMatrix,
sharedMatrixParameters
-} from '../../utilities/tests/matrix';
-import { hiddenWrapper } from '../../utilities/tests/hidden';
+} from '../../../utilities/tests/matrix';
+import { hiddenWrapper } from '../../../utilities/tests/hidden';
import { richTextEditorTag } from '..';
import {
cssPropertyFromTokenName,
tokenNames
-} from '../../theme-provider/design-token-names';
-import { buttonTag } from '../../button';
-import { loremIpsum } from '../../utilities/tests/lorem-ipsum';
+} from '../../../theme-provider/design-token-names';
+import { buttonTag } from '../../../button';
+import { loremIpsum } from '../../../utilities/tests/lorem-ipsum';
+import {
+ DisabledState,
+ ErrorState,
+ disabledStates,
+ errorStates
+} from '../../../utilities/tests/states';
const metadata: Meta = {
title: 'Tests/Rich Text Editor',
@@ -28,9 +34,43 @@ const richTextMarkdownString = '1. **Bold*Italics***';
export default metadata;
+const footerHiddenStates = [
+ ['Footer Visible', false],
+ ['Footer Hidden', true]
+] as const;
+type FooterHiddenState = (typeof footerHiddenStates)[number];
+
+const placeholderValueStates = [
+ ['', null],
+ ['Placeholder', 'Placeholder text']
+] as const;
+type PlaceholderValueStates = (typeof placeholderValueStates)[number];
+
// prettier-ignore
-const component = (): ViewTemplate => html`
- <${richTextEditorTag}>${richTextEditorTag}>
+const component = (
+ [disabledName, disabled]: DisabledState,
+ [footerHiddenName, footerHidden]: FooterHiddenState,
+ [errorStateName, isError, errorText]: ErrorState,
+ [placeholderName, placeholderText]: PlaceholderValueStates
+): ViewTemplate => html`
+
+ ${() => footerHiddenName} ${() => errorStateName} ${() => placeholderName} ${() => disabledName}
+
+ <${richTextEditorTag}
+ style="margin: 5px 0px; width: 500px;"
+ ?disabled="${() => disabled}"
+ ?footer-hidden="${() => footerHidden}"
+ ?error-visible="${() => isError}"
+ error-text="${() => errorText}"
+ placeholder="${() => placeholderText}"
+ >
+ ${richTextEditorTag}>
`;
const playFunction = (): void => {
@@ -38,15 +78,22 @@ const playFunction = (): void => {
editorNodeList.forEach(element => element.setMarkdown(richTextMarkdownString));
};
+const longTextPlayFunction = (): void => {
+ const editorNodeList = document.querySelectorAll('nimble-rich-text-editor');
+ editorNodeList.forEach(element => element.setMarkdown(
+ `${loremIpsum}\n\n **${loremIpsum}**\n\n ${loremIpsum}`
+ ));
+};
+
const editorSizingTestCase = (
[widthLabel, widthStyle]: [string, string],
[heightLabel, heightStyle]: [string, string]
): ViewTemplate => html`
${widthLabel}; ${heightLabel}
+ )}); margin-bottom: 0px;">${() => widthLabel}; ${() => heightLabel}
- <${richTextEditorTag} style="${widthStyle}; ${heightStyle};">
+ <${richTextEditorTag} style="${() => widthStyle}; ${() => heightStyle};">
<${buttonTag} slot="footer-actions" appearance="ghost">Cancel${buttonTag}>
<${buttonTag} slot="footer-actions" appearance="outline">Ok${buttonTag}>
${richTextEditorTag}>
@@ -54,11 +101,34 @@ const editorSizingTestCase = (
`;
export const richTextEditorThemeMatrix: StoryFn = createMatrixThemeStory(
- createMatrix(component)
+ createMatrix(component, [
+ disabledStates,
+ footerHiddenStates,
+ errorStates,
+ [placeholderValueStates[0]]
+ ])
);
-
richTextEditorThemeMatrix.play = playFunction;
+export const errorStateThemeMatrixWithLengthyContent: StoryFn = createMatrixThemeStory(
+ createMatrix(component, [
+ [disabledStates[0]],
+ [footerHiddenStates[0]],
+ errorStates,
+ [placeholderValueStates[0]]
+ ])
+);
+errorStateThemeMatrixWithLengthyContent.play = longTextPlayFunction;
+
+export const placeholderStateThemeMatrix: StoryFn = createMatrixThemeStory(
+ createMatrix(component, [
+ disabledStates,
+ [footerHiddenStates[0]],
+ [errorStates[0]],
+ placeholderValueStates
+ ])
+);
+
export const richTextEditorSizing: StoryFn = createStory(html`
${createMatrix(editorSizingTestCase, [
[
@@ -82,7 +152,6 @@ const mobileWidthComponent = html`
`;
export const plainTextContentInMobileWidth: StoryFn = createStory(mobileWidthComponent);
-
plainTextContentInMobileWidth.play = (): void => {
document.querySelector('nimble-rich-text-editor')!.setMarkdown(loremIpsum);
};
@@ -99,7 +168,6 @@ const multipleSubPointsContent = `
1. Sub point 9`;
export const multipleSubPointsContentInMobileWidth: StoryFn = createStory(mobileWidthComponent);
-
multipleSubPointsContentInMobileWidth.play = (): void => {
document
.querySelector('nimble-rich-text-editor')!
@@ -107,7 +175,6 @@ multipleSubPointsContentInMobileWidth.play = (): void => {
};
export const longWordContentInMobileWidth: StoryFn = createStory(mobileWidthComponent);
-
longWordContentInMobileWidth.play = (): void => {
document
.querySelector('nimble-rich-text-editor')!
@@ -115,6 +182,7 @@ longWordContentInMobileWidth.play = (): void => {
'ThisIsALongWordWithoutSpaceToTestLongWordInSmallWidthThisIsALongWordWithoutSpaceToTestLongWordInSmallWidth'
);
};
+
export const hiddenRichTextEditor: StoryFn = createStory(
hiddenWrapper(html`<${richTextEditorTag} hidden>${richTextEditorTag}>`)
);
diff --git a/packages/nimble-components/src/rich-text-editor/tests/rich-text-editor.spec.ts b/packages/nimble-components/src/rich-text/editor/tests/rich-text-editor.spec.ts
similarity index 86%
rename from packages/nimble-components/src/rich-text-editor/tests/rich-text-editor.spec.ts
rename to packages/nimble-components/src/rich-text/editor/tests/rich-text-editor.spec.ts
index 761db3fb45..7769ef0065 100644
--- a/packages/nimble-components/src/rich-text-editor/tests/rich-text-editor.spec.ts
+++ b/packages/nimble-components/src/rich-text/editor/tests/rich-text-editor.spec.ts
@@ -1,12 +1,13 @@
import { html } from '@microsoft/fast-element';
import { richTextEditorTag, RichTextEditor } from '..';
-import { type Fixture, fixture } from '../../utilities/tests/fixture';
-import { getSpecTypeByNamedList } from '../../utilities/tests/parameterized';
+import { type Fixture, fixture } from '../../../utilities/tests/fixture';
+import { getSpecTypeByNamedList } from '../../../utilities/tests/parameterized';
import { RichTextEditorPageObject } from '../testing/rich-text-editor.pageobject';
-import { wackyStrings } from '../../utilities/tests/wacky-strings';
-import type { Button } from '../../button';
-import type { ToggleButton } from '../../toggle-button';
+import { wackyStrings } from '../../../utilities/tests/wacky-strings';
+import type { Button } from '../../../button';
+import type { ToggleButton } from '../../../toggle-button';
import { ToolbarButton } from '../testing/types';
+import { createEventListener } from '../../../utilities/tests/component';
async function setup(): Promise> {
return fixture(
@@ -47,7 +48,7 @@ describe('RichTextEditor', () => {
it('should initialize Tiptap editor', () => {
expect(pageObject.editorSectionHasChildNodes()).toBeTrue();
expect(pageObject.getEditorSectionFirstElementChildClassName()).toBe(
- 'ProseMirror'
+ 'tiptap ProseMirror'
);
});
@@ -63,6 +64,34 @@ describe('RichTextEditor', () => {
expect(editor!.getAttribute('aria-multiline')).toBe('true');
});
+ it('should initialize "aria-label" with undefined when there is no "aria-label" set in the element', () => {
+ const editor = element.shadowRoot?.querySelector('.editor');
+
+ expect(editor!.hasAttribute('aria-label')).toBeFalse();
+ });
+
+ it('should forwards value of aria-label to internal control', () => {
+ const editor = element.shadowRoot?.querySelector('.editor');
+ element.ariaLabel = 'Rich Text Editor';
+
+ expect(editor!.getAttribute('aria-label')).toBe('Rich Text Editor');
+ });
+
+ it('should support setting blank "aria-label" value when setting empty string', () => {
+ const editor = element.shadowRoot?.querySelector('.editor');
+ element.ariaLabel = '';
+
+ expect(editor!.getAttribute('aria-label')).toBe('');
+ });
+
+ it('should remove value of aria-label from internal control when cleared from host', () => {
+ const editor = element.shadowRoot?.querySelector('.editor');
+ element.ariaLabel = 'not empty';
+ element.ariaLabel = null;
+
+ expect(editor!.getAttribute('aria-label')).toBeNull();
+ });
+
it('should have either one of the list buttons checked at the same time on click', async () => {
expect(
pageObject.getButtonCheckedState(ToolbarButton.bulletList)
@@ -146,7 +175,6 @@ describe('RichTextEditor', () => {
for (const value of formattingButtons) {
const specType = getSpecTypeByNamedList(value, focused, disabled);
- // eslint-disable-next-line @typescript-eslint/no-loop-func
specType(
`"${value.name}" button click check`,
// eslint-disable-next-line @typescript-eslint/no-loop-func
@@ -180,7 +208,6 @@ describe('RichTextEditor', () => {
for (const value of formattingButtons) {
const specType = getSpecTypeByNamedList(value, focused, disabled);
- // eslint-disable-next-line @typescript-eslint/no-loop-func
specType(
`"${value.name}" button key press check`,
// eslint-disable-next-line @typescript-eslint/no-loop-func
@@ -211,7 +238,6 @@ describe('RichTextEditor', () => {
for (const value of formattingButtons) {
const specType = getSpecTypeByNamedList(value, focused, disabled);
- // eslint-disable-next-line @typescript-eslint/no-loop-func
specType(
`"${value.name}" button key press check`,
// eslint-disable-next-line @typescript-eslint/no-loop-func
@@ -242,7 +268,6 @@ describe('RichTextEditor', () => {
for (const value of formattingButtons) {
const specType = getSpecTypeByNamedList(value, focused, disabled);
- // eslint-disable-next-line @typescript-eslint/no-loop-func
specType(
`"${value.name}" button keyboard shortcut check`,
// eslint-disable-next-line @typescript-eslint/no-loop-func
@@ -274,7 +299,6 @@ describe('RichTextEditor', () => {
for (const value of formattingButtons) {
const specType = getSpecTypeByNamedList(value, focused, disabled);
- // eslint-disable-next-line @typescript-eslint/no-loop-func
specType(
`"${value.name}" button not propagate change event to parent element`,
// eslint-disable-next-line @typescript-eslint/no-loop-func
@@ -603,7 +627,6 @@ describe('RichTextEditor', () => {
wackyStrings.forEach(value => {
const specType = getSpecTypeByNamedList(value, focused, disabled);
- // eslint-disable-next-line @typescript-eslint/no-loop-func
specType(
`wacky string "${value.name}" that are unmodified when rendered the same value within paragraph tag`,
// eslint-disable-next-line @typescript-eslint/no-loop-func
@@ -914,7 +937,6 @@ describe('RichTextEditor', () => {
const disabled: string[] = [];
for (const value of notSupportedMarkdownStrings) {
const specType = getSpecTypeByNamedList(value, focused, disabled);
- // eslint-disable-next-line @typescript-eslint/no-loop-func
specType(
`string "${value.name}" renders as plain text "${value.name}" within paragraph tag`,
// eslint-disable-next-line @typescript-eslint/no-loop-func
@@ -946,7 +968,6 @@ describe('RichTextEditor', () => {
focused,
disabled
);
- // eslint-disable-next-line @typescript-eslint/no-loop-func
specType(
`wacky string "${value.name}" that are unmodified when set the same "${value.name}" within paragraph tag`,
// eslint-disable-next-line @typescript-eslint/no-loop-func
@@ -982,7 +1003,6 @@ describe('RichTextEditor', () => {
for (const value of modifiedWackyStrings) {
const specType = getSpecTypeByNamedList(value, focused, disabled);
- // eslint-disable-next-line @typescript-eslint/no-loop-func
specType(
`wacky string "${value.name}" modified when rendered`,
// eslint-disable-next-line @typescript-eslint/no-loop-func
@@ -1146,7 +1166,6 @@ describe('RichTextEditor', () => {
const disabled: string[] = [];
for (const value of notSupportedMarkdownStrings) {
const specType = getSpecTypeByNamedList(value, focused, disabled);
- // eslint-disable-next-line @typescript-eslint/no-loop-func
specType(
`markdown string "${value.name}" returns as plain text "${value.name}" without any change`,
// eslint-disable-next-line @typescript-eslint/no-loop-func
@@ -1190,7 +1209,6 @@ describe('RichTextEditor', () => {
const disabled: string[] = [];
for (const value of specialMarkdownStrings) {
const specType = getSpecTypeByNamedList(value, focused, disabled);
- // eslint-disable-next-line @typescript-eslint/no-loop-func
specType(
`special markdown string "${value.name}" returns as plain text "${value.value}" with added esacpe character`,
// eslint-disable-next-line @typescript-eslint/no-loop-func
@@ -1223,7 +1241,6 @@ describe('RichTextEditor', () => {
focused,
disabled
);
- // eslint-disable-next-line @typescript-eslint/no-loop-func
specType(
`wacky string "${value.name}" returns unmodified when set the same markdown string"${value.name}"`,
// eslint-disable-next-line @typescript-eslint/no-loop-func
@@ -1254,9 +1271,8 @@ describe('RichTextEditor', () => {
const disabled: string[] = [];
for (const value of wackyStringWithSpecialMarkdownCharacter) {
const specType = getSpecTypeByNamedList(value, focused, disabled);
- // eslint-disable-next-line @typescript-eslint/no-loop-func
specType(
- ` wacky string contains special markdown syntax "${value.name}" returns as plain text "${value.value}" with added esacpe character`,
+ ` wacky string contains special markdown syntax "${value.name}" returns as plain text "${value.value}" with added escape character`,
// eslint-disable-next-line @typescript-eslint/no-loop-func
async () => {
element.setMarkdown(value.name);
@@ -1286,7 +1302,6 @@ describe('RichTextEditor', () => {
for (const value of modifiedWackyStrings) {
const specType = getSpecTypeByNamedList(value, focused, disabled);
- // eslint-disable-next-line @typescript-eslint/no-loop-func
specType(
`wacky string "${value.name}" returns modified when assigned`,
// eslint-disable-next-line @typescript-eslint/no-loop-func
@@ -1302,6 +1317,182 @@ describe('RichTextEditor', () => {
);
}
});
+
+ describe('disabled state', () => {
+ it('should reflect disabled value to the aria-disabled of editor-section', async () => {
+ const editor = element.shadowRoot?.querySelector('.editor');
+ expect(editor!.getAttribute('aria-disabled')).toBe('false');
+
+ await pageObject.setDisabled(true);
+
+ expect(editor!.getAttribute('aria-disabled')).toBe('true');
+ });
+
+ it('should reflect disabled value to the "contenteditable" attribute of tiptap editor', async () => {
+ const editor = element.shadowRoot?.querySelector('.ProseMirror');
+ expect(editor!.getAttribute('contenteditable')).toBe('true');
+
+ await pageObject.setDisabled(true);
+
+ expect(editor!.getAttribute('contenteditable')).toBe('false');
+ });
+
+ it('should enable the editor when "disabled" attribute is set and removed', async () => {
+ const editor = element.shadowRoot?.querySelector('.ProseMirror');
+ expect(pageObject.getEditorTabIndex()).toBe('0');
+
+ await pageObject.setDisabled(true);
+ await pageObject.setDisabled(false);
+
+ expect(editor!.getAttribute('contenteditable')).toBe('true');
+ });
+
+ it('should change the tabindex value of the editor when disabled value changes', async () => {
+ expect(pageObject.getEditorTabIndex()).toBe('0');
+
+ await pageObject.setDisabled(true);
+
+ expect(pageObject.getEditorTabIndex()).toBe('-1');
+ });
+
+ describe('should reflect disabled value to the disabled and aria-disabled state of toggle buttons', () => {
+ const focused: string[] = [];
+ const disabled: string[] = [];
+ for (const value of formattingButtons) {
+ const specType = getSpecTypeByNamedList(
+ value,
+ focused,
+ disabled
+ );
+ specType(
+ `for "${value.name}" button`,
+ // eslint-disable-next-line @typescript-eslint/no-loop-func
+ async () => {
+ expect(
+ pageObject.isButtonDisabled(
+ value.toolbarButtonIndex
+ )
+ ).toBeFalse();
+
+ await pageObject.setDisabled(true);
+
+ expect(
+ pageObject.isButtonDisabled(
+ value.toolbarButtonIndex
+ )
+ ).toBeTrue();
+ }
+ );
+ }
+ });
+ });
+
+ it('should hide the footer when "footer-hidden" attribute is enabled', async () => {
+ expect(pageObject.isFooterHidden()).toBeFalse();
+
+ await pageObject.setFooterHidden(true);
+
+ expect(pageObject.isFooterHidden()).toBeTrue();
+ });
+
+ it('should show the footer when "footer-hidden" attribute is disabled', async () => {
+ expect(pageObject.isFooterHidden()).toBeFalse();
+
+ await pageObject.setFooterHidden(true);
+ await pageObject.setFooterHidden(false);
+
+ expect(pageObject.isFooterHidden()).toBeFalse();
+ });
+
+ it('should fire "input" event when there is an input to the editor', async () => {
+ const inputEventListener = createEventListener(element, 'input');
+
+ await pageObject.setEditorTextContent('input');
+ await inputEventListener.promise;
+
+ expect(inputEventListener.spy).toHaveBeenCalledTimes(1);
+ });
+
+ it('should not fire "input" event when setting the content through "setMarkdown"', () => {
+ const inputEventListener = createEventListener(element, 'input');
+
+ element.setMarkdown('input');
+
+ expect(inputEventListener.spy).not.toHaveBeenCalled();
+ });
+
+ it('should fire "input" event when the text is updated/removed from the editor', async () => {
+ const inputEventListener = createEventListener(element, 'input');
+
+ await pageObject.setEditorTextContent('update');
+ await inputEventListener.promise;
+
+ expect(inputEventListener.spy).toHaveBeenCalledTimes(1);
+
+ await pageObject.setEditorTextContent('');
+ await inputEventListener.promise;
+
+ expect(inputEventListener.spy).toHaveBeenCalledTimes(1);
+ });
+
+ it('should initialize "empty" to true and set false when there is content', async () => {
+ expect(element.empty).toBeTrue();
+
+ await pageObject.setEditorTextContent('not empty');
+ expect(element.empty).toBeFalse();
+
+ await pageObject.setEditorTextContent('');
+ expect(element.empty).toBeTrue();
+ });
+
+ it('should update "empty" when the content is loaded with "setMarkdown"', () => {
+ expect(element.empty).toBeTrue();
+
+ element.setMarkdown('not empty');
+ expect(element.empty).toBeFalse();
+
+ element.setMarkdown('');
+ expect(element.empty).toBeTrue();
+ });
+
+ it('should return true for "empty" if there is only whitespace', async () => {
+ expect(element.empty).toBeTrue();
+
+ await pageObject.setEditorTextContent(' ');
+ expect(element.empty).toBeTrue();
+
+ element.setMarkdown(' ');
+ expect(element.empty).toBeTrue();
+ });
+
+ it('should return true for "empty" even if the placeholder content is set', () => {
+ expect(element.empty).toBeTrue();
+
+ element.placeholder = 'Placeholder text';
+ expect(element.empty).toBeTrue();
+ });
+
+ it('should initialize the "placeholder" attribute with undefined', () => {
+ expect(element.placeholder).toBeUndefined();
+ });
+
+ it('should reflect the "placeholder" value to its internal attribute', () => {
+ expect(pageObject.getPlaceholderValue()).toBe('');
+
+ element.placeholder = 'Placeholder text';
+
+ expect(pageObject.getPlaceholderValue()).toBe('Placeholder text');
+ });
+
+ it('should set "placeholder" value to empty when attribute is cleared with an empty string', () => {
+ element.placeholder = 'Placeholder text';
+
+ expect(pageObject.getPlaceholderValue()).toBe('Placeholder text');
+
+ element.placeholder = '';
+
+ expect(pageObject.getPlaceholderValue()).toBe('');
+ });
});
describe('RichTextEditor Before DOM Connection', () => {
diff --git a/packages/nimble-components/src/rich-text-editor/tests/rich-text-editor.stories.ts b/packages/nimble-components/src/rich-text/editor/tests/rich-text-editor.stories.ts
similarity index 71%
rename from packages/nimble-components/src/rich-text-editor/tests/rich-text-editor.stories.ts
rename to packages/nimble-components/src/rich-text/editor/tests/rich-text-editor.stories.ts
index ff2adc95cf..b068e3a376 100644
--- a/packages/nimble-components/src/rich-text-editor/tests/rich-text-editor.stories.ts
+++ b/packages/nimble-components/src/rich-text/editor/tests/rich-text-editor.stories.ts
@@ -1,11 +1,12 @@
import { html, ref, when } from '@microsoft/fast-element';
import type { Meta, StoryObj } from '@storybook/html';
+import { withActions } from '@storybook/addon-actions/decorator';
import {
createUserSelectedThemeStory,
incubatingWarning
-} from '../../utilities/tests/storybook';
+} from '../../../utilities/tests/storybook';
import { RichTextEditor, richTextEditorTag } from '..';
-import { buttonTag } from '../../button';
+import { buttonTag } from '../../../button';
// eslint-disable-next-line @typescript-eslint/no-empty-interface
interface RichTextEditorArgs {
@@ -14,6 +15,13 @@ interface RichTextEditorArgs {
getMarkdown: undefined;
editorRef: RichTextEditor;
setMarkdownData: (args: RichTextEditorArgs) => void;
+ disabled: boolean;
+ footerHidden: boolean;
+ errorVisible: boolean;
+ errorText: string;
+ input: unknown;
+ empty: unknown;
+ placeholder: string;
}
type ExampleDataType = (typeof exampleDataType)[keyof typeof exampleDataType];
@@ -54,11 +62,15 @@ client application must implement that functionality.
const metadata: Meta = {
title: 'Incubating/Rich Text Editor',
tags: ['autodocs'],
+ decorators: [withActions],
parameters: {
docs: {
description: {
component: richTextEditorDescription
}
+ },
+ actions: {
+ handles: ['input']
}
},
// prettier-ignore
@@ -70,6 +82,11 @@ const metadata: Meta = {
<${richTextEditorTag}
${ref('editorRef')}
data-unused="${x => x.setMarkdownData(x)}"
+ ?disabled="${x => x.disabled}"
+ ?footer-hidden="${x => x.footerHidden}"
+ ?error-visible="${x => x.errorVisible}"
+ error-text="${x => x.errorText}"
+ placeholder="${x => x.placeholder}"
>
${when(x => x.footerActionButtons, html`
<${buttonTag} appearance="ghost" slot="footer-actions">Cancel${buttonTag}>
@@ -103,11 +120,43 @@ const metadata: Meta = {
},
setMarkdownData: {
table: { disable: true }
+ },
+ errorVisible: {
+ description:
+ 'Whether the editor should be styled to indicate that it is in an invalid state.'
+ },
+ errorText: {
+ description:
+ 'A message to be displayed when the editor is in the invalid state explaining why the value is invalid.'
+ },
+ placeholder: {
+ description: 'Placeholder text to show when editor is empty.'
+ },
+ footerHidden: {
+ description:
+ 'Setting `footer-hidden` hides the footer section which consists of all formatting option buttons and the `footer-actions` slot content.'
+ },
+ empty: {
+ name: 'empty',
+ description:
+ 'Read-only boolean value. Returns true if editor is either empty or contains only whitespace.',
+ control: false
+ },
+ input: {
+ name: 'input',
+ description:
+ 'This event is fired when there is a change in the content of the editor.',
+ control: false
}
},
args: {
data: exampleDataType.plainString,
footerActionButtons: false,
+ disabled: false,
+ footerHidden: false,
+ errorVisible: false,
+ errorText: 'Value is invalid',
+ placeholder: 'Placeholder',
editorRef: undefined,
setMarkdownData: x => {
void (async () => {
diff --git a/packages/nimble-components/src/rich-text-editor/tests/types.spec.ts b/packages/nimble-components/src/rich-text/editor/tests/types.spec.ts
similarity index 100%
rename from packages/nimble-components/src/rich-text-editor/tests/types.spec.ts
rename to packages/nimble-components/src/rich-text/editor/tests/types.spec.ts
diff --git a/packages/nimble-components/src/rich-text-viewer/index.ts b/packages/nimble-components/src/rich-text/models/markdown-parser.ts
similarity index 55%
rename from packages/nimble-components/src/rich-text-viewer/index.ts
rename to packages/nimble-components/src/rich-text/models/markdown-parser.ts
index 132daa978d..ab06283a96 100644
--- a/packages/nimble-components/src/rich-text-viewer/index.ts
+++ b/packages/nimble-components/src/rich-text/models/markdown-parser.ts
@@ -1,60 +1,36 @@
-import { DesignSystem, FoundationElement } from '@microsoft/fast-foundation';
import {
schema,
defaultMarkdownParser,
MarkdownParser
} from 'prosemirror-markdown';
import { DOMSerializer } from 'prosemirror-model';
-import { observable } from '@microsoft/fast-element';
-import { template } from './template';
-import { styles } from './styles';
-
-declare global {
- interface HTMLElementTagNameMap {
- 'nimble-rich-text-viewer': RichTextViewer;
- }
-}
/**
- * A nimble styled rich text viewer
+ * Provides markdown parser for rich text components
*/
-export class RichTextViewer extends FoundationElement {
- /**
- *
- * @public
- * Markdown string to render its corresponding rich text content in the component.
- */
- @observable
- public markdown = '';
-
- /**
- * @internal
- */
- public viewer!: HTMLDivElement;
+export class RichTextMarkdownParser {
private readonly markdownParser: MarkdownParser;
private readonly domSerializer: DOMSerializer;
public constructor() {
- super();
- this.domSerializer = DOMSerializer.fromSchema(schema);
this.markdownParser = this.initializeMarkdownParser();
+ this.domSerializer = DOMSerializer.fromSchema(schema);
}
/**
- * @internal
- */
- public override connectedCallback(): void {
- super.connectedCallback();
- this.updateView();
- }
-
- /**
- * @internal
+ *
+ * This function takes a markdown string, parses it using the ProseMirror MarkdownParser, serializes the parsed content into a
+ * DOM structure using a DOMSerializer, and returns the serialized result.
+ * If the markdown parser returns null, it will clear the viewer component by creating an empty document fragment.
*/
- public markdownChanged(): void {
- if (this.$fastController.isConnected) {
- this.updateView();
+ public parseMarkdownToDOM(value: string): HTMLElement | DocumentFragment {
+ const parsedMarkdownContent = this.markdownParser.parse(value);
+ if (parsedMarkdownContent === null) {
+ return document.createDocumentFragment();
}
+ return this.domSerializer.serializeFragment(
+ parsedMarkdownContent.content
+ );
}
private initializeMarkdownParser(): MarkdownParser {
@@ -80,41 +56,4 @@ export class RichTextViewer extends FoundationElement {
defaultMarkdownParser.tokens
);
}
-
- /**
- *
- * This function takes a markdown string, parses it using the ProseMirror MarkdownParser, serializes the parsed content into a
- * DOM structure using a DOMSerializer, and returns the serialized result.
- * If the markdown parser returns null, it will clear the viewer component by creating an empty document fragment.
- */
- private parseMarkdownToDOM(value: string): HTMLElement | DocumentFragment {
- const parsedMarkdownContent = this.markdownParser.parse(value);
- if (parsedMarkdownContent === null) {
- return document.createDocumentFragment();
- }
-
- return this.domSerializer.serializeFragment(
- parsedMarkdownContent.content
- );
- }
-
- private updateView(): void {
- if (this.markdown) {
- const serializedContent = this.parseMarkdownToDOM(this.markdown);
- this.viewer.replaceChildren(serializedContent);
- } else {
- this.viewer.innerHTML = '';
- }
- }
}
-
-const nimbleRichTextViewer = RichTextViewer.compose({
- baseName: 'rich-text-viewer',
- template,
- styles
-});
-
-DesignSystem.getOrCreate()
- .withPrefix('nimble')
- .register(nimbleRichTextViewer());
-export const richTextViewerTag = DesignSystem.tagFor(RichTextViewer);
diff --git a/packages/nimble-components/src/rich-text/models/markdown-serializer.ts b/packages/nimble-components/src/rich-text/models/markdown-serializer.ts
new file mode 100644
index 0000000000..231bd0dad4
--- /dev/null
+++ b/packages/nimble-components/src/rich-text/models/markdown-serializer.ts
@@ -0,0 +1,47 @@
+import {
+ MarkdownSerializer,
+ defaultMarkdownSerializer,
+ MarkdownSerializerState
+} from 'prosemirror-markdown';
+import type { Node } from 'prosemirror-model';
+
+export function richTextMarkdownSerializer(): MarkdownSerializer {
+ /**
+ * orderedList Node is getting 'order' attribute which it is not present in the
+ * tip-tap orderedList Node and having start instead of order, Changed it to start (nodes.attrs.start)
+ * Assigned updated node in place of orderedList node from defaultMarkdownSerializer
+ * https://github.com/ProseMirror/prosemirror-markdown/blob/b7c1fd2fb74c7564bfe5428c7c8141ded7ebdd9f/src/to_markdown.ts#L94C2-L101C7
+ */
+ const orderedListNode = function orderedList(
+ state: MarkdownSerializerState,
+ node: Node
+ ): void {
+ const start = (node.attrs.start as number) || 1;
+ const maxW = String(start + node.childCount - 1).length;
+ const space = state.repeat(' ', maxW + 2);
+ state.renderList(node, space, i => {
+ const nStr = String(start + i);
+ return `${state.repeat(' ', maxW - nStr.length) + nStr}. `;
+ });
+ };
+
+ /**
+ * Internally Tiptap editor creates it own schema ( Nodes AND Marks ) based on the extensions ( Here Starter Kit is used for Bold, italic, orderedList and
+ * bulletList extensions) and defaultMarkdownSerializer uses schema from prosemirror-markdown to serialize the markdown.
+ * So, there is variations in the nodes and marks name (Eg. 'ordered_list' in prosemirror-markdown schema whereas 'orderedList' in tip tap editor schema),
+ * To fix up this reassigned the respective nodes and marks with tip-tap editor schema.
+ */
+ const nodes = {
+ bulletList: defaultMarkdownSerializer.nodes.bullet_list!,
+ listItem: defaultMarkdownSerializer.nodes.list_item!,
+ orderedList: orderedListNode,
+ doc: defaultMarkdownSerializer.nodes.doc!,
+ paragraph: defaultMarkdownSerializer.nodes.paragraph!,
+ text: defaultMarkdownSerializer.nodes.text!
+ };
+ const marks = {
+ italic: defaultMarkdownSerializer.marks.em!,
+ bold: defaultMarkdownSerializer.marks.strong!
+ };
+ return new MarkdownSerializer(nodes, marks);
+}
diff --git a/packages/nimble-components/src/rich-text/viewer/index.ts b/packages/nimble-components/src/rich-text/viewer/index.ts
new file mode 100644
index 0000000000..dcc5146380
--- /dev/null
+++ b/packages/nimble-components/src/rich-text/viewer/index.ts
@@ -0,0 +1,75 @@
+import { DesignSystem, FoundationElement } from '@microsoft/fast-foundation';
+import { observable } from '@microsoft/fast-element';
+import { template } from './template';
+import { styles } from './styles';
+import { RichTextMarkdownParser } from '../models/markdown-parser';
+
+declare global {
+ interface HTMLElementTagNameMap {
+ 'nimble-rich-text-viewer': RichTextViewer;
+ }
+}
+
+/**
+ * A nimble styled rich text viewer
+ */
+export class RichTextViewer extends FoundationElement {
+ /**
+ *
+ * @public
+ * Markdown string to render its corresponding rich text content in the component.
+ */
+ @observable
+ public markdown = '';
+
+ /**
+ * @internal
+ */
+ public viewer!: HTMLDivElement;
+
+ private readonly markdownParser: RichTextMarkdownParser;
+
+ public constructor() {
+ super();
+ this.markdownParser = new RichTextMarkdownParser();
+ }
+
+ /**
+ * @internal
+ */
+ public override connectedCallback(): void {
+ super.connectedCallback();
+ this.updateView();
+ }
+
+ /**
+ * @internal
+ */
+ public markdownChanged(): void {
+ if (this.$fastController.isConnected) {
+ this.updateView();
+ }
+ }
+
+ private updateView(): void {
+ if (this.markdown) {
+ const serializedContent = this.markdownParser.parseMarkdownToDOM(
+ this.markdown
+ );
+ this.viewer.replaceChildren(serializedContent);
+ } else {
+ this.viewer.innerHTML = '';
+ }
+ }
+}
+
+const nimbleRichTextViewer = RichTextViewer.compose({
+ baseName: 'rich-text-viewer',
+ template,
+ styles
+});
+
+DesignSystem.getOrCreate()
+ .withPrefix('nimble')
+ .register(nimbleRichTextViewer());
+export const richTextViewerTag = DesignSystem.tagFor(RichTextViewer);
diff --git a/packages/nimble-components/src/rich-text-viewer/specs/README.md b/packages/nimble-components/src/rich-text/viewer/specs/README.md
similarity index 100%
rename from packages/nimble-components/src/rich-text-viewer/specs/README.md
rename to packages/nimble-components/src/rich-text/viewer/specs/README.md
diff --git a/packages/nimble-components/src/rich-text-viewer/styles.ts b/packages/nimble-components/src/rich-text/viewer/styles.ts
similarity index 96%
rename from packages/nimble-components/src/rich-text-viewer/styles.ts
rename to packages/nimble-components/src/rich-text/viewer/styles.ts
index f40f0522c6..0d9d8fcbfa 100644
--- a/packages/nimble-components/src/rich-text-viewer/styles.ts
+++ b/packages/nimble-components/src/rich-text/viewer/styles.ts
@@ -5,7 +5,7 @@ import {
bodyFontColor,
linkActiveFontColor,
linkFontColor
-} from '../theme-provider/design-tokens';
+} from '../../theme-provider/design-tokens';
export const styles = css`
${display('flex')}
diff --git a/packages/nimble-components/src/rich-text-viewer/template.ts b/packages/nimble-components/src/rich-text/viewer/template.ts
similarity index 100%
rename from packages/nimble-components/src/rich-text-viewer/template.ts
rename to packages/nimble-components/src/rich-text/viewer/template.ts
diff --git a/packages/nimble-components/src/rich-text-viewer/testing/rich-text-viewer.pageobject.ts b/packages/nimble-components/src/rich-text/viewer/testing/rich-text-viewer.pageobject.ts
similarity index 100%
rename from packages/nimble-components/src/rich-text-viewer/testing/rich-text-viewer.pageobject.ts
rename to packages/nimble-components/src/rich-text/viewer/testing/rich-text-viewer.pageobject.ts
diff --git a/packages/nimble-components/src/rich-text-viewer/tests/rich-text-viewer-matrix.stories.ts b/packages/nimble-components/src/rich-text/viewer/tests/rich-text-viewer-matrix.stories.ts
similarity index 91%
rename from packages/nimble-components/src/rich-text-viewer/tests/rich-text-viewer-matrix.stories.ts
rename to packages/nimble-components/src/rich-text/viewer/tests/rich-text-viewer-matrix.stories.ts
index 7c91e47565..8e4f36ff3d 100644
--- a/packages/nimble-components/src/rich-text-viewer/tests/rich-text-viewer-matrix.stories.ts
+++ b/packages/nimble-components/src/rich-text/viewer/tests/rich-text-viewer-matrix.stories.ts
@@ -3,19 +3,19 @@ import { html, ViewTemplate } from '@microsoft/fast-element';
import {
createMatrixThemeStory,
createStory
-} from '../../utilities/tests/storybook';
+} from '../../../utilities/tests/storybook';
import {
createMatrix,
sharedMatrixParameters
-} from '../../utilities/tests/matrix';
-import { hiddenWrapper } from '../../utilities/tests/hidden';
+} from '../../../utilities/tests/matrix';
+import { hiddenWrapper } from '../../../utilities/tests/hidden';
import { richTextViewerTag } from '..';
-import { richTextMarkdownString } from '../../utilities/tests/rich-text-markdown-string';
-import { loremIpsum } from '../../utilities/tests/lorem-ipsum';
+import { richTextMarkdownString } from '../../../utilities/tests/rich-text-markdown-string';
+import { loremIpsum } from '../../../utilities/tests/lorem-ipsum';
import {
cssPropertyFromTokenName,
tokenNames
-} from '../../theme-provider/design-token-names';
+} from '../../../theme-provider/design-token-names';
const metadata: Meta = {
title: 'Tests/Rich Text Viewer',
diff --git a/packages/nimble-components/src/rich-text-viewer/tests/rich-text-viewer.spec.ts b/packages/nimble-components/src/rich-text/viewer/tests/rich-text-viewer.spec.ts
similarity index 99%
rename from packages/nimble-components/src/rich-text-viewer/tests/rich-text-viewer.spec.ts
rename to packages/nimble-components/src/rich-text/viewer/tests/rich-text-viewer.spec.ts
index 1235e90ba1..726feb4a93 100644
--- a/packages/nimble-components/src/rich-text-viewer/tests/rich-text-viewer.spec.ts
+++ b/packages/nimble-components/src/rich-text/viewer/tests/rich-text-viewer.spec.ts
@@ -1,9 +1,9 @@
import { html } from '@microsoft/fast-element';
-import { RichTextViewer, richTextViewerTag } from '..';
-import { fixture, type Fixture } from '../../utilities/tests/fixture';
+import { fixture, type Fixture } from '../../../utilities/tests/fixture';
import { RichTextViewerPageObject } from '../testing/rich-text-viewer.pageobject';
-import { wackyStrings } from '../../utilities/tests/wacky-strings';
-import { getSpecTypeByNamedList } from '../../utilities/tests/parameterized';
+import { wackyStrings } from '../../../utilities/tests/wacky-strings';
+import { getSpecTypeByNamedList } from '../../../utilities/tests/parameterized';
+import { RichTextViewer, richTextViewerTag } from '..';
async function setup(): Promise> {
return fixture(
diff --git a/packages/nimble-components/src/rich-text-viewer/tests/rich-text-viewer.stories.ts b/packages/nimble-components/src/rich-text/viewer/tests/rich-text-viewer.stories.ts
similarity index 92%
rename from packages/nimble-components/src/rich-text-viewer/tests/rich-text-viewer.stories.ts
rename to packages/nimble-components/src/rich-text/viewer/tests/rich-text-viewer.stories.ts
index cf1e2f14ac..25dfe832e5 100644
--- a/packages/nimble-components/src/rich-text-viewer/tests/rich-text-viewer.stories.ts
+++ b/packages/nimble-components/src/rich-text/viewer/tests/rich-text-viewer.stories.ts
@@ -3,9 +3,9 @@ import type { Meta, StoryObj } from '@storybook/html';
import {
createUserSelectedThemeStory,
incubatingWarning
-} from '../../utilities/tests/storybook';
+} from '../../../utilities/tests/storybook';
import { richTextViewerTag } from '..';
-import { richTextMarkdownString } from '../../utilities/tests/rich-text-markdown-string';
+import { richTextMarkdownString } from '../../../utilities/tests/rich-text-markdown-string';
interface RichTextViewerArgs {
markdown: string;
From fa86b5a78f927b771efa5c3bd357f04b713edf77 Mon Sep 17 00:00:00 2001
From: "SOLITONTECH\\vivin.krishna"
<123377523+vivinkrishna-ni@users.noreply.github.com>
Date: Thu, 31 Aug 2023 13:17:31 +0530
Subject: [PATCH 02/19] Fix component import in angular
---
.../rich-text-viewer/nimble-rich-text-viewer.directive.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/angular-workspace/projects/ni/nimble-angular/rich-text-viewer/nimble-rich-text-viewer.directive.ts b/angular-workspace/projects/ni/nimble-angular/rich-text-viewer/nimble-rich-text-viewer.directive.ts
index 1440b0d0c0..7609f35d8d 100644
--- a/angular-workspace/projects/ni/nimble-angular/rich-text-viewer/nimble-rich-text-viewer.directive.ts
+++ b/angular-workspace/projects/ni/nimble-angular/rich-text-viewer/nimble-rich-text-viewer.directive.ts
@@ -1,5 +1,5 @@
import { Directive, ElementRef, Input, Renderer2 } from '@angular/core';
-import type { RichTextViewer } from '@ni/nimble-components/dist/esm/rich-text-viewer';
+import type { RichTextViewer } from '@ni/nimble-components/dist/esm/rich-text/viewer';
export type { RichTextViewer };
From c7f895dd2790e5dfdb5b7eb06969cb648901acba Mon Sep 17 00:00:00 2001
From: "SOLITONTECH\\vivin.krishna"
<123377523+vivinkrishna-ni@users.noreply.github.com>
Date: Thu, 31 Aug 2023 13:33:44 +0530
Subject: [PATCH 03/19] Fixing build issue in angular viewer module
---
.../rich-text-viewer/nimble-rich-text-viewer.module.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/angular-workspace/projects/ni/nimble-angular/rich-text-viewer/nimble-rich-text-viewer.module.ts b/angular-workspace/projects/ni/nimble-angular/rich-text-viewer/nimble-rich-text-viewer.module.ts
index c94455a3a8..d3bc69cf06 100644
--- a/angular-workspace/projects/ni/nimble-angular/rich-text-viewer/nimble-rich-text-viewer.module.ts
+++ b/angular-workspace/projects/ni/nimble-angular/rich-text-viewer/nimble-rich-text-viewer.module.ts
@@ -2,7 +2,7 @@ import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { NimbleRichTextViewerDirective } from './nimble-rich-text-viewer.directive';
-import '@ni/nimble-components/dist/esm/rich-text-viewer';
+import '@ni/nimble-components/dist/esm/rich-text/viewer';
@NgModule({
declarations: [NimbleRichTextViewerDirective],
From 01afe477046952ea45b7e97225051687184c014c Mon Sep 17 00:00:00 2001
From: "SOLITONTECH\\vivin.krishna"
<123377523+vivinkrishna-ni@users.noreply.github.com>
Date: Thu, 31 Aug 2023 15:08:48 +0530
Subject: [PATCH 04/19] Updated the markdown serializer class
---
.../src/rich-text/editor/index.ts | 9 +-
.../src/rich-text/models/markdown-parser.ts | 1 -
.../rich-text/models/markdown-serializer.ts | 91 +++++++++++--------
3 files changed, 56 insertions(+), 45 deletions(-)
diff --git a/packages/nimble-components/src/rich-text/editor/index.ts b/packages/nimble-components/src/rich-text/editor/index.ts
index 173e7a9cf7..68b79e63cf 100644
--- a/packages/nimble-components/src/rich-text/editor/index.ts
+++ b/packages/nimble-components/src/rich-text/editor/index.ts
@@ -23,7 +23,7 @@ import { styles } from './styles';
import type { ToggleButton } from '../../toggle-button';
import type { ErrorPattern } from '../../patterns/error/types';
import { RichTextMarkdownParser } from '../models/markdown-parser';
-import { richTextMarkdownSerializer } from '../models/markdown-serializer';
+import { RichTextMarkdownSerializer } from '../models/markdown-serializer';
declare global {
interface HTMLElementTagNameMap {
@@ -140,7 +140,7 @@ export class RichTextEditor extends FoundationElement implements ErrorPattern {
private updateScrollbarWidthQueued = false;
private readonly markdownParser = new RichTextMarkdownParser();
- private readonly markdownSerializer = richTextMarkdownSerializer();
+ private readonly markdownSerializer = new RichTextMarkdownSerializer();
private readonly xmlSerializer = new XMLSerializer();
/**
@@ -300,10 +300,7 @@ export class RichTextEditor extends FoundationElement implements ErrorPattern {
* @public
*/
public getMarkdown(): string {
- const markdownContent = this.markdownSerializer.serialize(
- this.tiptapEditor.state.doc
- );
- return markdownContent;
+ return this.markdownSerializer.serializeToMarkdown(this.tiptapEditor.state.doc);
}
/**
diff --git a/packages/nimble-components/src/rich-text/models/markdown-parser.ts b/packages/nimble-components/src/rich-text/models/markdown-parser.ts
index ab06283a96..9a1ae9a2c4 100644
--- a/packages/nimble-components/src/rich-text/models/markdown-parser.ts
+++ b/packages/nimble-components/src/rich-text/models/markdown-parser.ts
@@ -18,7 +18,6 @@ export class RichTextMarkdownParser {
}
/**
- *
* This function takes a markdown string, parses it using the ProseMirror MarkdownParser, serializes the parsed content into a
* DOM structure using a DOMSerializer, and returns the serialized result.
* If the markdown parser returns null, it will clear the viewer component by creating an empty document fragment.
diff --git a/packages/nimble-components/src/rich-text/models/markdown-serializer.ts b/packages/nimble-components/src/rich-text/models/markdown-serializer.ts
index 231bd0dad4..1f859ac17a 100644
--- a/packages/nimble-components/src/rich-text/models/markdown-serializer.ts
+++ b/packages/nimble-components/src/rich-text/models/markdown-serializer.ts
@@ -5,43 +5,58 @@ import {
} from 'prosemirror-markdown';
import type { Node } from 'prosemirror-model';
-export function richTextMarkdownSerializer(): MarkdownSerializer {
- /**
- * orderedList Node is getting 'order' attribute which it is not present in the
- * tip-tap orderedList Node and having start instead of order, Changed it to start (nodes.attrs.start)
- * Assigned updated node in place of orderedList node from defaultMarkdownSerializer
- * https://github.com/ProseMirror/prosemirror-markdown/blob/b7c1fd2fb74c7564bfe5428c7c8141ded7ebdd9f/src/to_markdown.ts#L94C2-L101C7
- */
- const orderedListNode = function orderedList(
- state: MarkdownSerializerState,
- node: Node
- ): void {
- const start = (node.attrs.start as number) || 1;
- const maxW = String(start + node.childCount - 1).length;
- const space = state.repeat(' ', maxW + 2);
- state.renderList(node, space, i => {
- const nStr = String(start + i);
- return `${state.repeat(' ', maxW - nStr.length) + nStr}. `;
- });
- };
+/**
+ * Provides markdown serializer for rich text components
+ */
+export class RichTextMarkdownSerializer {
+ private readonly markdownSerializer: MarkdownSerializer;
- /**
- * Internally Tiptap editor creates it own schema ( Nodes AND Marks ) based on the extensions ( Here Starter Kit is used for Bold, italic, orderedList and
- * bulletList extensions) and defaultMarkdownSerializer uses schema from prosemirror-markdown to serialize the markdown.
- * So, there is variations in the nodes and marks name (Eg. 'ordered_list' in prosemirror-markdown schema whereas 'orderedList' in tip tap editor schema),
- * To fix up this reassigned the respective nodes and marks with tip-tap editor schema.
- */
- const nodes = {
- bulletList: defaultMarkdownSerializer.nodes.bullet_list!,
- listItem: defaultMarkdownSerializer.nodes.list_item!,
- orderedList: orderedListNode,
- doc: defaultMarkdownSerializer.nodes.doc!,
- paragraph: defaultMarkdownSerializer.nodes.paragraph!,
- text: defaultMarkdownSerializer.nodes.text!
- };
- const marks = {
- italic: defaultMarkdownSerializer.marks.em!,
- bold: defaultMarkdownSerializer.marks.strong!
- };
- return new MarkdownSerializer(nodes, marks);
+ public constructor() {
+ this.markdownSerializer = this.initializeMarkdownSerializer();
+ }
+
+ public serializeToMarkdown(doc: Node): string {
+ return this.markdownSerializer.serialize(doc);
+ }
+
+ private initializeMarkdownSerializer(): MarkdownSerializer {
+ /**
+ * orderedList Node is getting 'order' attribute which it is not present in the
+ * tip-tap orderedList Node and having start instead of order, Changed it to start (nodes.attrs.start)
+ * Assigned updated node in place of orderedList node from defaultMarkdownSerializer
+ * https://github.com/ProseMirror/prosemirror-markdown/blob/b7c1fd2fb74c7564bfe5428c7c8141ded7ebdd9f/src/to_markdown.ts#L94C2-L101C7
+ */
+ const orderedListNode = function orderedList(
+ state: MarkdownSerializerState,
+ node: Node
+ ): void {
+ const start = (node.attrs.start as number) || 1;
+ const maxW = String(start + node.childCount - 1).length;
+ const space = state.repeat(' ', maxW + 2);
+ state.renderList(node, space, i => {
+ const nStr = String(start + i);
+ return `${state.repeat(' ', maxW - nStr.length) + nStr}. `;
+ });
+ };
+
+ /**
+ * Internally Tiptap editor creates it own schema ( Nodes AND Marks ) based on the extensions ( Here Starter Kit is used for Bold, italic, orderedList and
+ * bulletList extensions) and defaultMarkdownSerializer uses schema from prosemirror-markdown to serialize the markdown.
+ * So, there is variations in the nodes and marks name (Eg. 'ordered_list' in prosemirror-markdown schema whereas 'orderedList' in tip tap editor schema),
+ * To fix up this reassigned the respective nodes and marks with tip-tap editor schema.
+ */
+ const nodes = {
+ bulletList: defaultMarkdownSerializer.nodes.bullet_list!,
+ listItem: defaultMarkdownSerializer.nodes.list_item!,
+ orderedList: orderedListNode,
+ doc: defaultMarkdownSerializer.nodes.doc!,
+ paragraph: defaultMarkdownSerializer.nodes.paragraph!,
+ text: defaultMarkdownSerializer.nodes.text!
+ };
+ const marks = {
+ italic: defaultMarkdownSerializer.marks.em!,
+ bold: defaultMarkdownSerializer.marks.strong!
+ };
+ return new MarkdownSerializer(nodes, marks);
+ }
}
From bafbe48ffe3fd126a5ed7143b9b8cfe92fde3d7f Mon Sep 17 00:00:00 2001
From: "SOLITONTECH\\vivin.krishna"
<123377523+vivinkrishna-ni@users.noreply.github.com>
Date: Thu, 31 Aug 2023 15:56:01 +0530
Subject: [PATCH 05/19] Renaming the initialization markdown serializer method
---
.../src/rich-text/models/markdown-serializer.ts | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/packages/nimble-components/src/rich-text/models/markdown-serializer.ts b/packages/nimble-components/src/rich-text/models/markdown-serializer.ts
index 1f859ac17a..0b2436a47b 100644
--- a/packages/nimble-components/src/rich-text/models/markdown-serializer.ts
+++ b/packages/nimble-components/src/rich-text/models/markdown-serializer.ts
@@ -12,14 +12,14 @@ export class RichTextMarkdownSerializer {
private readonly markdownSerializer: MarkdownSerializer;
public constructor() {
- this.markdownSerializer = this.initializeMarkdownSerializer();
+ this.markdownSerializer = this.initializeMarkdownSerializerForTipTap();
}
public serializeToMarkdown(doc: Node): string {
return this.markdownSerializer.serialize(doc);
}
- private initializeMarkdownSerializer(): MarkdownSerializer {
+ private initializeMarkdownSerializerForTipTap(): MarkdownSerializer {
/**
* orderedList Node is getting 'order' attribute which it is not present in the
* tip-tap orderedList Node and having start instead of order, Changed it to start (nodes.attrs.start)
From 91bb520c481337193b592e77b493952f6318cb41 Mon Sep 17 00:00:00 2001
From: "SOLITONTECH\\vivin.krishna"
<123377523+vivinkrishna-ni@users.noreply.github.com>
Date: Thu, 31 Aug 2023 16:29:50 +0530
Subject: [PATCH 06/19] Fix lint errors
---
packages/nimble-components/src/rich-text/editor/index.ts | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/packages/nimble-components/src/rich-text/editor/index.ts b/packages/nimble-components/src/rich-text/editor/index.ts
index 68b79e63cf..59815ce72c 100644
--- a/packages/nimble-components/src/rich-text/editor/index.ts
+++ b/packages/nimble-components/src/rich-text/editor/index.ts
@@ -300,7 +300,9 @@ export class RichTextEditor extends FoundationElement implements ErrorPattern {
* @public
*/
public getMarkdown(): string {
- return this.markdownSerializer.serializeToMarkdown(this.tiptapEditor.state.doc);
+ return this.markdownSerializer.serializeToMarkdown(
+ this.tiptapEditor.state.doc
+ );
}
/**
From 3a701565de86f2e4b339939b41b39714f43c51ee Mon Sep 17 00:00:00 2001
From: "SOLITONTECH\\vivin.krishna"
<123377523+vivinkrishna-ni@users.noreply.github.com>
Date: Thu, 31 Aug 2023 16:32:16 +0530
Subject: [PATCH 07/19] Change files
---
...imble-angular-3a7f08ef-dd7e-4bae-8b30-937d3830d563.json | 7 +++++++
...le-components-c9aef77f-1a41-4240-b114-67fc1d5c6c2f.json | 7 +++++++
2 files changed, 14 insertions(+)
create mode 100644 change/@ni-nimble-angular-3a7f08ef-dd7e-4bae-8b30-937d3830d563.json
create mode 100644 change/@ni-nimble-components-c9aef77f-1a41-4240-b114-67fc1d5c6c2f.json
diff --git a/change/@ni-nimble-angular-3a7f08ef-dd7e-4bae-8b30-937d3830d563.json b/change/@ni-nimble-angular-3a7f08ef-dd7e-4bae-8b30-937d3830d563.json
new file mode 100644
index 0000000000..4cc1292b1d
--- /dev/null
+++ b/change/@ni-nimble-angular-3a7f08ef-dd7e-4bae-8b30-937d3830d563.json
@@ -0,0 +1,7 @@
+{
+ "type": "patch",
+ "comment": "Revamp rich text components folder structure",
+ "packageName": "@ni/nimble-angular",
+ "email": "123377523+vivinkrishna-ni@users.noreply.github.com",
+ "dependentChangeType": "patch"
+}
diff --git a/change/@ni-nimble-components-c9aef77f-1a41-4240-b114-67fc1d5c6c2f.json b/change/@ni-nimble-components-c9aef77f-1a41-4240-b114-67fc1d5c6c2f.json
new file mode 100644
index 0000000000..e64898b55e
--- /dev/null
+++ b/change/@ni-nimble-components-c9aef77f-1a41-4240-b114-67fc1d5c6c2f.json
@@ -0,0 +1,7 @@
+{
+ "type": "patch",
+ "comment": "Update folder paths of rich text components in the nimble-angular",
+ "packageName": "@ni/nimble-components",
+ "email": "123377523+vivinkrishna-ni@users.noreply.github.com",
+ "dependentChangeType": "patch"
+}
From d37e001ebcbd0ca77487df463a0453ea99d0aa88 Mon Sep 17 00:00:00 2001
From: "SOLITONTECH\\vivin.krishna"
<123377523+vivinkrishna-ni@users.noreply.github.com>
Date: Thu, 31 Aug 2023 16:33:45 +0530
Subject: [PATCH 08/19] Update change file description
---
...@ni-nimble-angular-3a7f08ef-dd7e-4bae-8b30-937d3830d563.json | 2 +-
...-nimble-components-c9aef77f-1a41-4240-b114-67fc1d5c6c2f.json | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/change/@ni-nimble-angular-3a7f08ef-dd7e-4bae-8b30-937d3830d563.json b/change/@ni-nimble-angular-3a7f08ef-dd7e-4bae-8b30-937d3830d563.json
index 4cc1292b1d..8edd5163fd 100644
--- a/change/@ni-nimble-angular-3a7f08ef-dd7e-4bae-8b30-937d3830d563.json
+++ b/change/@ni-nimble-angular-3a7f08ef-dd7e-4bae-8b30-937d3830d563.json
@@ -1,6 +1,6 @@
{
"type": "patch",
- "comment": "Revamp rich text components folder structure",
+ "comment": "Update folder paths for importing rich text components",
"packageName": "@ni/nimble-angular",
"email": "123377523+vivinkrishna-ni@users.noreply.github.com",
"dependentChangeType": "patch"
diff --git a/change/@ni-nimble-components-c9aef77f-1a41-4240-b114-67fc1d5c6c2f.json b/change/@ni-nimble-components-c9aef77f-1a41-4240-b114-67fc1d5c6c2f.json
index e64898b55e..9c790f46ee 100644
--- a/change/@ni-nimble-components-c9aef77f-1a41-4240-b114-67fc1d5c6c2f.json
+++ b/change/@ni-nimble-components-c9aef77f-1a41-4240-b114-67fc1d5c6c2f.json
@@ -1,6 +1,6 @@
{
"type": "patch",
- "comment": "Update folder paths of rich text components in the nimble-angular",
+ "comment": "Revamp rich text components folder structure",
"packageName": "@ni/nimble-components",
"email": "123377523+vivinkrishna-ni@users.noreply.github.com",
"dependentChangeType": "patch"
From 2c2d5ac7edaddc535a68e6a9775d748de60eb347 Mon Sep 17 00:00:00 2001
From: "SOLITONTECH\\vivin.krishna"
<123377523+vivinkrishna-ni@users.noreply.github.com>
Date: Fri, 1 Sep 2023 10:13:10 +0530
Subject: [PATCH 09/19] Resolve merge conflicts
---
.../src/rich-text/editor/index.ts | 17 +++++++++++++++++
1 file changed, 17 insertions(+)
diff --git a/packages/nimble-components/src/rich-text/editor/index.ts b/packages/nimble-components/src/rich-text/editor/index.ts
index 59815ce72c..41b750cdba 100644
--- a/packages/nimble-components/src/rich-text/editor/index.ts
+++ b/packages/nimble-components/src/rich-text/editor/index.ts
@@ -212,6 +212,7 @@ export class RichTextEditor extends FoundationElement implements ErrorPattern {
*/
public boldButtonClick(): void {
this.tiptapEditor.chain().focus().toggleBold().run();
+ this.forceFocusEditor();
}
/**
@@ -221,6 +222,7 @@ export class RichTextEditor extends FoundationElement implements ErrorPattern {
public boldButtonKeyDown(event: KeyboardEvent): boolean {
if (this.keyActivatesButton(event)) {
this.tiptapEditor.chain().focus().toggleBold().run();
+ this.forceFocusEditor();
return false;
}
return true;
@@ -232,6 +234,7 @@ export class RichTextEditor extends FoundationElement implements ErrorPattern {
*/
public italicsButtonClick(): void {
this.tiptapEditor.chain().focus().toggleItalic().run();
+ this.forceFocusEditor();
}
/**
@@ -241,6 +244,7 @@ export class RichTextEditor extends FoundationElement implements ErrorPattern {
public italicsButtonKeyDown(event: KeyboardEvent): boolean {
if (this.keyActivatesButton(event)) {
this.tiptapEditor.chain().focus().toggleItalic().run();
+ this.forceFocusEditor();
return false;
}
return true;
@@ -252,6 +256,7 @@ export class RichTextEditor extends FoundationElement implements ErrorPattern {
*/
public bulletListButtonClick(): void {
this.tiptapEditor.chain().focus().toggleBulletList().run();
+ this.forceFocusEditor();
}
/**
@@ -261,6 +266,7 @@ export class RichTextEditor extends FoundationElement implements ErrorPattern {
public bulletListButtonKeyDown(event: KeyboardEvent): boolean {
if (this.keyActivatesButton(event)) {
this.tiptapEditor.chain().focus().toggleBulletList().run();
+ this.forceFocusEditor();
return false;
}
return true;
@@ -272,6 +278,7 @@ export class RichTextEditor extends FoundationElement implements ErrorPattern {
*/
public numberedListButtonClick(): void {
this.tiptapEditor.chain().focus().toggleOrderedList().run();
+ this.forceFocusEditor();
}
/**
@@ -281,6 +288,7 @@ export class RichTextEditor extends FoundationElement implements ErrorPattern {
public numberedListButtonKeyDown(event: KeyboardEvent): boolean {
if (this.keyActivatesButton(event)) {
this.tiptapEditor.chain().focus().toggleOrderedList().run();
+ this.forceFocusEditor();
return false;
}
return true;
@@ -462,6 +470,15 @@ export class RichTextEditor extends FoundationElement implements ErrorPattern {
}
});
}
+
+ // In Firefox browser, once the editor gets focused, the blinking caret will be visible until we click format buttons (Bold, Italic ...) in the Firefox browser (changing focus).
+ // But once any of the toolbar button is clicked, editor internally has its focus but the blinking caret disappears.
+ // As a workaround, manually triggering blur and setting focus on editor makes the blinking caret to re-appear.
+ // Mozilla issue https://bugzilla.mozilla.org/show_bug.cgi?id=1496769 tracks removal of this workaround.
+ private forceFocusEditor(): void {
+ this.tiptapEditor.commands.blur();
+ this.tiptapEditor.commands.focus();
+ }
}
// eslint-disable-next-line @typescript-eslint/no-empty-interface
From b7fe9d8dc2a90053fb0fd4b283c5174d20f53dfb Mon Sep 17 00:00:00 2001
From: "SOLITONTECH\\vivin.krishna"
<123377523+vivinkrishna-ni@users.noreply.github.com>
Date: Fri, 1 Sep 2023 10:18:03 +0530
Subject: [PATCH 10/19] Update editor component paths for rich text editor
---
.../rich-text-editor/nimble-rich-text-editor.directive.ts | 2 +-
.../rich-text-editor/nimble-rich-text-editor.module.ts | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/angular-workspace/projects/ni/nimble-angular/rich-text-editor/nimble-rich-text-editor.directive.ts b/angular-workspace/projects/ni/nimble-angular/rich-text-editor/nimble-rich-text-editor.directive.ts
index abd5a2a0a8..865b5f0439 100644
--- a/angular-workspace/projects/ni/nimble-angular/rich-text-editor/nimble-rich-text-editor.directive.ts
+++ b/angular-workspace/projects/ni/nimble-angular/rich-text-editor/nimble-rich-text-editor.directive.ts
@@ -1,5 +1,5 @@
import { Directive, ElementRef, EventEmitter, HostListener, Input, Output, Renderer2 } from '@angular/core';
-import type { RichTextEditor } from '@ni/nimble-components/dist/esm/rich-text-editor';
+import type { RichTextEditor } from '@ni/nimble-components/dist/esm/rich-text/editor';
import { BooleanValueOrAttribute, toBooleanProperty } from '@ni/nimble-angular/internal-utilities';
export type { RichTextEditor };
diff --git a/angular-workspace/projects/ni/nimble-angular/rich-text-editor/nimble-rich-text-editor.module.ts b/angular-workspace/projects/ni/nimble-angular/rich-text-editor/nimble-rich-text-editor.module.ts
index 0c6c57ce98..4164c9f09b 100644
--- a/angular-workspace/projects/ni/nimble-angular/rich-text-editor/nimble-rich-text-editor.module.ts
+++ b/angular-workspace/projects/ni/nimble-angular/rich-text-editor/nimble-rich-text-editor.module.ts
@@ -2,7 +2,7 @@ import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { NimbleRichTextEditorDirective } from './nimble-rich-text-editor.directive';
-import '@ni/nimble-components/dist/esm/rich-text-editor';
+import '@ni/nimble-components/dist/esm/rich-text/editor';
@NgModule({
declarations: [NimbleRichTextEditorDirective],
From b819d0e73c2af9b023fe0dcde90563b6a3c97cc4 Mon Sep 17 00:00:00 2001
From: "SOLITONTECH\\vivin.krishna"
<123377523+vivinkrishna-ni@users.noreply.github.com>
Date: Fri, 1 Sep 2023 10:36:14 +0530
Subject: [PATCH 11/19] Updating pageobject paths for angular files
---
.../rich-text-editor/testing/rich-text-editor.pageobject.ts | 4 ++--
.../rich-text-viewer/testing/rich-text-viewer.pageobject.ts | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/angular-workspace/projects/ni/nimble-angular/rich-text-editor/testing/rich-text-editor.pageobject.ts b/angular-workspace/projects/ni/nimble-angular/rich-text-editor/testing/rich-text-editor.pageobject.ts
index 075882fc1b..3047e7e51a 100644
--- a/angular-workspace/projects/ni/nimble-angular/rich-text-editor/testing/rich-text-editor.pageobject.ts
+++ b/angular-workspace/projects/ni/nimble-angular/rich-text-editor/testing/rich-text-editor.pageobject.ts
@@ -1,5 +1,5 @@
-import { RichTextEditorPageObject } from '@ni/nimble-components/dist/esm/rich-text-editor/testing/rich-text-editor.pageobject';
-import type { ToolbarButton } from '@ni/nimble-components/dist/esm/rich-text-editor/testing/types';
+import { RichTextEditorPageObject } from '@ni/nimble-components/dist/esm/rich-text/editor/testing/rich-text-editor.pageobject';
+import type { ToolbarButton } from '@ni/nimble-components/dist/esm/rich-text/editor/testing/types';
export { RichTextEditorPageObject };
export type { ToolbarButton };
\ No newline at end of file
diff --git a/angular-workspace/projects/ni/nimble-angular/rich-text-viewer/testing/rich-text-viewer.pageobject.ts b/angular-workspace/projects/ni/nimble-angular/rich-text-viewer/testing/rich-text-viewer.pageobject.ts
index 0deb789be0..84b9fedbbb 100644
--- a/angular-workspace/projects/ni/nimble-angular/rich-text-viewer/testing/rich-text-viewer.pageobject.ts
+++ b/angular-workspace/projects/ni/nimble-angular/rich-text-viewer/testing/rich-text-viewer.pageobject.ts
@@ -1,3 +1,3 @@
-import { RichTextViewerPageObject } from '@ni/nimble-components/dist/esm/rich-text-viewer/testing/rich-text-viewer.pageobject';
+import { RichTextViewerPageObject } from '@ni/nimble-components/dist/esm/rich-text/viewer/testing/rich-text-viewer.pageobject';
export { RichTextViewerPageObject };
\ No newline at end of file
From 4acdff35737cea1a3f919e0dff0f98673ced1c43 Mon Sep 17 00:00:00 2001
From: "SOLITONTECH\\vivin.krishna"
<123377523+vivinkrishna-ni@users.noreply.github.com>
Date: Fri, 1 Sep 2023 11:34:03 +0530
Subject: [PATCH 12/19] Resolve merge conflicts
---
.../src/rich-text/editor/index.ts | 20 +++++++++++++++----
.../editor/tests/rich-text-editor.spec.ts | 4 ++--
.../src/rich-text/editor/tests/types.spec.ts | 9 +++++++++
.../src/rich-text/editor/types.ts | 11 ++++++++++
4 files changed, 38 insertions(+), 6 deletions(-)
create mode 100644 packages/nimble-components/src/rich-text/editor/types.ts
diff --git a/packages/nimble-components/src/rich-text/editor/index.ts b/packages/nimble-components/src/rich-text/editor/index.ts
index 41b750cdba..56a3d9b349 100644
--- a/packages/nimble-components/src/rich-text/editor/index.ts
+++ b/packages/nimble-components/src/rich-text/editor/index.ts
@@ -6,7 +6,13 @@ import {
FoundationElement
} from '@microsoft/fast-foundation';
import { keyEnter, keySpace } from '@microsoft/fast-web-utilities';
-import { Editor, AnyExtension, Extension } from '@tiptap/core';
+import {
+ Editor,
+ findParentNode,
+ isList,
+ AnyExtension,
+ Extension
+} from '@tiptap/core';
import Bold from '@tiptap/extension-bold';
import BulletList from '@tiptap/extension-bullet-list';
import Document from '@tiptap/extension-document';
@@ -22,6 +28,7 @@ import { template } from './template';
import { styles } from './styles';
import type { ToggleButton } from '../../toggle-button';
import type { ErrorPattern } from '../../patterns/error/types';
+import { TipTapNodeName } from './types';
import { RichTextMarkdownParser } from '../models/markdown-parser';
import { RichTextMarkdownSerializer } from '../models/markdown-serializer';
@@ -382,10 +389,15 @@ export class RichTextEditor extends FoundationElement implements ErrorPattern {
}
private updateEditorButtonsState(): void {
+ const { extensionManager, state } = this.tiptapEditor;
+ const { extensions } = extensionManager;
+ const { selection } = state;
+ const parentList = findParentNode((node: { type: { name: string } }) => isList(node.type.name, extensions))(selection);
+
this.boldButton.checked = this.tiptapEditor.isActive('bold');
this.italicsButton.checked = this.tiptapEditor.isActive('italic');
- this.bulletListButton.checked = this.tiptapEditor.isActive('bulletList');
- this.numberedListButton.checked = this.tiptapEditor.isActive('orderedList');
+ this.bulletListButton.checked = parentList?.node.type.name === TipTapNodeName.bulletList;
+ this.numberedListButton.checked = parentList?.node.type.name === TipTapNodeName.numberedList;
}
private keyActivatesButton(event: KeyboardEvent): boolean {
@@ -457,7 +469,7 @@ export class RichTextEditor extends FoundationElement implements ErrorPattern {
extensionName: string
): AnyExtension | undefined {
return this.tiptapEditor.extensionManager.extensions.find(
- extension => extension.name === extensionName
+ (extension: { name: string }) => extension.name === extensionName
);
}
diff --git a/packages/nimble-components/src/rich-text/editor/tests/rich-text-editor.spec.ts b/packages/nimble-components/src/rich-text/editor/tests/rich-text-editor.spec.ts
index 5196f43885..4c486996a0 100644
--- a/packages/nimble-components/src/rich-text/editor/tests/rich-text-editor.spec.ts
+++ b/packages/nimble-components/src/rich-text/editor/tests/rich-text-editor.spec.ts
@@ -452,7 +452,7 @@ describe('RichTextEditor', () => {
]);
expect(
pageObject.getButtonCheckedState(ToolbarButton.numberedList)
- ).toBeTrue();
+ ).toBeFalse();
expect(
pageObject.getButtonCheckedState(ToolbarButton.bulletList)
).toBeTrue();
@@ -534,7 +534,7 @@ describe('RichTextEditor', () => {
).toBeTrue();
expect(
pageObject.getButtonCheckedState(ToolbarButton.bulletList)
- ).toBeTrue();
+ ).toBeFalse();
});
it('should have "ul" tag names for bullet lists when clicking "tab" to make it nested and "shift+Tab" to make it usual list', async () => {
diff --git a/packages/nimble-components/src/rich-text/editor/tests/types.spec.ts b/packages/nimble-components/src/rich-text/editor/tests/types.spec.ts
index 4f52aecc3b..5f142d7813 100644
--- a/packages/nimble-components/src/rich-text/editor/tests/types.spec.ts
+++ b/packages/nimble-components/src/rich-text/editor/tests/types.spec.ts
@@ -1,4 +1,5 @@
import type { ToolbarButton } from '../testing/types';
+import type { TipTapNodeName } from '../types';
describe('Editor Toolbar button page object types', () => {
it('ToolbarButton fails compile if assigning arbitrary string values', () => {
@@ -8,3 +9,11 @@ describe('Editor Toolbar button page object types', () => {
expect(value).toEqual('hello');
});
});
+
+describe('Tiptap node types', () => {
+ it('TipTapNodeName fails compile if assigning arbitrary string values', () => {
+ // @ts-expect-error This expect will fail if the enum-like type is missing "as const"
+ const value: TipTapNodeName = 'hello';
+ expect(value).toEqual('hello');
+ });
+});
diff --git a/packages/nimble-components/src/rich-text/editor/types.ts b/packages/nimble-components/src/rich-text/editor/types.ts
new file mode 100644
index 0000000000..e402f2de45
--- /dev/null
+++ b/packages/nimble-components/src/rich-text/editor/types.ts
@@ -0,0 +1,11 @@
+/**
+ * TipTap node types.
+ * @public
+ */
+export const TipTapNodeName = {
+ bulletList: 'bulletList',
+ numberedList: 'orderedList'
+} as const;
+
+export type TipTapNodeName =
+ (typeof TipTapNodeName)[keyof typeof TipTapNodeName];
From fdc3cf7edc6f7aec1b4397f497efe9a7353f815f Mon Sep 17 00:00:00 2001
From: Vivin Krishna <123377523+vivinkrishna-ni@users.noreply.github.com>
Date: Fri, 1 Sep 2023 18:09:20 +0530
Subject: [PATCH 13/19] Updated serialize method name
---
packages/nimble-components/src/rich-text/editor/index.ts | 2 +-
.../src/rich-text/models/markdown-serializer.ts | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/packages/nimble-components/src/rich-text/editor/index.ts b/packages/nimble-components/src/rich-text/editor/index.ts
index b096b8e7d4..8b8006e957 100644
--- a/packages/nimble-components/src/rich-text/editor/index.ts
+++ b/packages/nimble-components/src/rich-text/editor/index.ts
@@ -315,7 +315,7 @@ export class RichTextEditor extends FoundationElement implements ErrorPattern {
* @public
*/
public getMarkdown(): string {
- return this.markdownSerializer.serializeToMarkdown(
+ return this.markdownSerializer.serializeDOMToMarkdown(
this.tiptapEditor.state.doc
);
}
diff --git a/packages/nimble-components/src/rich-text/models/markdown-serializer.ts b/packages/nimble-components/src/rich-text/models/markdown-serializer.ts
index 0b2436a47b..e6fbae74a8 100644
--- a/packages/nimble-components/src/rich-text/models/markdown-serializer.ts
+++ b/packages/nimble-components/src/rich-text/models/markdown-serializer.ts
@@ -15,7 +15,7 @@ export class RichTextMarkdownSerializer {
this.markdownSerializer = this.initializeMarkdownSerializerForTipTap();
}
- public serializeToMarkdown(doc: Node): string {
+ public serializeDOMToMarkdown(doc: Node): string {
return this.markdownSerializer.serialize(doc);
}
From 65acbd701836b28aa090d493bc35d4c4f040e334 Mon Sep 17 00:00:00 2001
From: Vivin Krishna <123377523+vivinkrishna-ni@users.noreply.github.com>
Date: Fri, 1 Sep 2023 19:28:16 +0530
Subject: [PATCH 14/19] Moved the parser initialization in viewer component
just like the editor and removed constructor
---
packages/nimble-components/src/rich-text/viewer/index.ts | 7 +------
1 file changed, 1 insertion(+), 6 deletions(-)
diff --git a/packages/nimble-components/src/rich-text/viewer/index.ts b/packages/nimble-components/src/rich-text/viewer/index.ts
index dcc5146380..6d0b86dc2d 100644
--- a/packages/nimble-components/src/rich-text/viewer/index.ts
+++ b/packages/nimble-components/src/rich-text/viewer/index.ts
@@ -27,12 +27,7 @@ export class RichTextViewer extends FoundationElement {
*/
public viewer!: HTMLDivElement;
- private readonly markdownParser: RichTextMarkdownParser;
-
- public constructor() {
- super();
- this.markdownParser = new RichTextMarkdownParser();
- }
+ private readonly markdownParser = new RichTextMarkdownParser();
/**
* @internal
From b38df31c8b9aba847b5e2aadfc9af80a40b43081 Mon Sep 17 00:00:00 2001
From: Vivin Krishna <123377523+vivinkrishna-ni@users.noreply.github.com>
Date: Mon, 4 Sep 2023 09:36:10 +0530
Subject: [PATCH 15/19] Resolve PR comments
---
.../projects/example-client-app/src/app/app.module.ts | 4 ++--
.../src/app/customapp/customapp.component.ts | 2 +-
.../{rich-text-editor => rich-text/editor}/ng-package.json | 0
.../editor}/nimble-rich-text-editor.directive.ts | 0
.../editor}/nimble-rich-text-editor.module.ts | 0
.../{rich-text-editor => rich-text/editor}/public-api.ts | 0
.../editor}/testing/ng-package.json | 0
.../editor}/testing/public-api.ts | 0
.../editor}/testing/rich-text-editor.pageobject.ts | 0
.../editor}/tests/nimble-rich-text-editor.directive.spec.ts | 0
.../{rich-text-viewer => rich-text/viewer}/ng-package.json | 0
.../viewer}/nimble-rich-text-viewer.directive.ts | 0
.../viewer}/nimble-rich-text-viewer.module.ts | 0
.../{rich-text-viewer => rich-text/viewer}/public-api.ts | 0
.../viewer}/testing/ng-package.json | 0
.../viewer}/testing/public-api.ts | 0
.../viewer}/testing/rich-text-viewer.pageobject.ts | 0
.../viewer}/tests/nimble-rich-text-viewer.directive.spec.ts | 0
...nimble-angular-3a7f08ef-dd7e-4bae-8b30-937d3830d563.json | 2 +-
...ble-components-c9aef77f-1a41-4240-b114-67fc1d5c6c2f.json | 2 +-
.../src/rich-text/models/markdown-serializer.ts | 6 +-----
.../nimble-components/src/rich-text/viewer/specs/README.md | 2 +-
22 files changed, 7 insertions(+), 11 deletions(-)
rename angular-workspace/projects/ni/nimble-angular/{rich-text-editor => rich-text/editor}/ng-package.json (100%)
rename angular-workspace/projects/ni/nimble-angular/{rich-text-editor => rich-text/editor}/nimble-rich-text-editor.directive.ts (100%)
rename angular-workspace/projects/ni/nimble-angular/{rich-text-editor => rich-text/editor}/nimble-rich-text-editor.module.ts (100%)
rename angular-workspace/projects/ni/nimble-angular/{rich-text-editor => rich-text/editor}/public-api.ts (100%)
rename angular-workspace/projects/ni/nimble-angular/{rich-text-editor => rich-text/editor}/testing/ng-package.json (100%)
rename angular-workspace/projects/ni/nimble-angular/{rich-text-editor => rich-text/editor}/testing/public-api.ts (100%)
rename angular-workspace/projects/ni/nimble-angular/{rich-text-editor => rich-text/editor}/testing/rich-text-editor.pageobject.ts (100%)
rename angular-workspace/projects/ni/nimble-angular/{rich-text-editor => rich-text/editor}/tests/nimble-rich-text-editor.directive.spec.ts (100%)
rename angular-workspace/projects/ni/nimble-angular/{rich-text-viewer => rich-text/viewer}/ng-package.json (100%)
rename angular-workspace/projects/ni/nimble-angular/{rich-text-viewer => rich-text/viewer}/nimble-rich-text-viewer.directive.ts (100%)
rename angular-workspace/projects/ni/nimble-angular/{rich-text-viewer => rich-text/viewer}/nimble-rich-text-viewer.module.ts (100%)
rename angular-workspace/projects/ni/nimble-angular/{rich-text-viewer => rich-text/viewer}/public-api.ts (100%)
rename angular-workspace/projects/ni/nimble-angular/{rich-text-viewer => rich-text/viewer}/testing/ng-package.json (100%)
rename angular-workspace/projects/ni/nimble-angular/{rich-text-viewer => rich-text/viewer}/testing/public-api.ts (100%)
rename angular-workspace/projects/ni/nimble-angular/{rich-text-viewer => rich-text/viewer}/testing/rich-text-viewer.pageobject.ts (100%)
rename angular-workspace/projects/ni/nimble-angular/{rich-text-viewer => rich-text/viewer}/tests/nimble-rich-text-viewer.directive.spec.ts (100%)
diff --git a/angular-workspace/projects/example-client-app/src/app/app.module.ts b/angular-workspace/projects/example-client-app/src/app/app.module.ts
index 3b92310d7e..6f59580b53 100644
--- a/angular-workspace/projects/example-client-app/src/app/app.module.ts
+++ b/angular-workspace/projects/example-client-app/src/app/app.module.ts
@@ -22,8 +22,8 @@ import { NimbleTableColumnDateTextModule } from '@ni/nimble-angular/table-column
import { NimbleTableColumnEnumTextModule } from '@ni/nimble-angular/table-column/enum-text';
import { NimbleTableColumnIconModule } from '@ni/nimble-angular/table-column/icon';
import { NimbleTableColumnNumberTextModule } from '@ni/nimble-angular/table-column/number-text';
-import { NimbleRichTextViewerModule } from '@ni/nimble-angular/rich-text-viewer';
-import { NimbleRichTextEditorModule } from '@ni/nimble-angular/rich-text-editor';
+import { NimbleRichTextViewerModule } from '@ni/nimble-angular/rich-text/viewer';
+import { NimbleRichTextEditorModule } from '@ni/nimble-angular/rich-text/editor';
import { AppComponent } from './app.component';
import { CustomAppComponent } from './customapp/customapp.component';
import { HeaderComponent } from './header/header.component';
diff --git a/angular-workspace/projects/example-client-app/src/app/customapp/customapp.component.ts b/angular-workspace/projects/example-client-app/src/app/customapp/customapp.component.ts
index 21c3a5c6a0..62455a4309 100644
--- a/angular-workspace/projects/example-client-app/src/app/customapp/customapp.component.ts
+++ b/angular-workspace/projects/example-client-app/src/app/customapp/customapp.component.ts
@@ -3,7 +3,7 @@ import { Component, Inject, ViewChild } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { DrawerLocation, MenuItem, NimbleDialogDirective, NimbleDrawerDirective, OptionNotFound, OPTION_NOT_FOUND, UserDismissed } from '@ni/nimble-angular';
import type { TableRecord } from '@ni/nimble-angular/table';
-import { NimbleRichTextEditorDirective } from '@ni/nimble-angular/rich-text-editor';
+import { NimbleRichTextEditorDirective } from '@ni/nimble-angular/rich-text/editor';
import { BehaviorSubject, Observable } from 'rxjs';
interface ComboboxItem {
diff --git a/angular-workspace/projects/ni/nimble-angular/rich-text-editor/ng-package.json b/angular-workspace/projects/ni/nimble-angular/rich-text/editor/ng-package.json
similarity index 100%
rename from angular-workspace/projects/ni/nimble-angular/rich-text-editor/ng-package.json
rename to angular-workspace/projects/ni/nimble-angular/rich-text/editor/ng-package.json
diff --git a/angular-workspace/projects/ni/nimble-angular/rich-text-editor/nimble-rich-text-editor.directive.ts b/angular-workspace/projects/ni/nimble-angular/rich-text/editor/nimble-rich-text-editor.directive.ts
similarity index 100%
rename from angular-workspace/projects/ni/nimble-angular/rich-text-editor/nimble-rich-text-editor.directive.ts
rename to angular-workspace/projects/ni/nimble-angular/rich-text/editor/nimble-rich-text-editor.directive.ts
diff --git a/angular-workspace/projects/ni/nimble-angular/rich-text-editor/nimble-rich-text-editor.module.ts b/angular-workspace/projects/ni/nimble-angular/rich-text/editor/nimble-rich-text-editor.module.ts
similarity index 100%
rename from angular-workspace/projects/ni/nimble-angular/rich-text-editor/nimble-rich-text-editor.module.ts
rename to angular-workspace/projects/ni/nimble-angular/rich-text/editor/nimble-rich-text-editor.module.ts
diff --git a/angular-workspace/projects/ni/nimble-angular/rich-text-editor/public-api.ts b/angular-workspace/projects/ni/nimble-angular/rich-text/editor/public-api.ts
similarity index 100%
rename from angular-workspace/projects/ni/nimble-angular/rich-text-editor/public-api.ts
rename to angular-workspace/projects/ni/nimble-angular/rich-text/editor/public-api.ts
diff --git a/angular-workspace/projects/ni/nimble-angular/rich-text-editor/testing/ng-package.json b/angular-workspace/projects/ni/nimble-angular/rich-text/editor/testing/ng-package.json
similarity index 100%
rename from angular-workspace/projects/ni/nimble-angular/rich-text-editor/testing/ng-package.json
rename to angular-workspace/projects/ni/nimble-angular/rich-text/editor/testing/ng-package.json
diff --git a/angular-workspace/projects/ni/nimble-angular/rich-text-editor/testing/public-api.ts b/angular-workspace/projects/ni/nimble-angular/rich-text/editor/testing/public-api.ts
similarity index 100%
rename from angular-workspace/projects/ni/nimble-angular/rich-text-editor/testing/public-api.ts
rename to angular-workspace/projects/ni/nimble-angular/rich-text/editor/testing/public-api.ts
diff --git a/angular-workspace/projects/ni/nimble-angular/rich-text-editor/testing/rich-text-editor.pageobject.ts b/angular-workspace/projects/ni/nimble-angular/rich-text/editor/testing/rich-text-editor.pageobject.ts
similarity index 100%
rename from angular-workspace/projects/ni/nimble-angular/rich-text-editor/testing/rich-text-editor.pageobject.ts
rename to angular-workspace/projects/ni/nimble-angular/rich-text/editor/testing/rich-text-editor.pageobject.ts
diff --git a/angular-workspace/projects/ni/nimble-angular/rich-text-editor/tests/nimble-rich-text-editor.directive.spec.ts b/angular-workspace/projects/ni/nimble-angular/rich-text/editor/tests/nimble-rich-text-editor.directive.spec.ts
similarity index 100%
rename from angular-workspace/projects/ni/nimble-angular/rich-text-editor/tests/nimble-rich-text-editor.directive.spec.ts
rename to angular-workspace/projects/ni/nimble-angular/rich-text/editor/tests/nimble-rich-text-editor.directive.spec.ts
diff --git a/angular-workspace/projects/ni/nimble-angular/rich-text-viewer/ng-package.json b/angular-workspace/projects/ni/nimble-angular/rich-text/viewer/ng-package.json
similarity index 100%
rename from angular-workspace/projects/ni/nimble-angular/rich-text-viewer/ng-package.json
rename to angular-workspace/projects/ni/nimble-angular/rich-text/viewer/ng-package.json
diff --git a/angular-workspace/projects/ni/nimble-angular/rich-text-viewer/nimble-rich-text-viewer.directive.ts b/angular-workspace/projects/ni/nimble-angular/rich-text/viewer/nimble-rich-text-viewer.directive.ts
similarity index 100%
rename from angular-workspace/projects/ni/nimble-angular/rich-text-viewer/nimble-rich-text-viewer.directive.ts
rename to angular-workspace/projects/ni/nimble-angular/rich-text/viewer/nimble-rich-text-viewer.directive.ts
diff --git a/angular-workspace/projects/ni/nimble-angular/rich-text-viewer/nimble-rich-text-viewer.module.ts b/angular-workspace/projects/ni/nimble-angular/rich-text/viewer/nimble-rich-text-viewer.module.ts
similarity index 100%
rename from angular-workspace/projects/ni/nimble-angular/rich-text-viewer/nimble-rich-text-viewer.module.ts
rename to angular-workspace/projects/ni/nimble-angular/rich-text/viewer/nimble-rich-text-viewer.module.ts
diff --git a/angular-workspace/projects/ni/nimble-angular/rich-text-viewer/public-api.ts b/angular-workspace/projects/ni/nimble-angular/rich-text/viewer/public-api.ts
similarity index 100%
rename from angular-workspace/projects/ni/nimble-angular/rich-text-viewer/public-api.ts
rename to angular-workspace/projects/ni/nimble-angular/rich-text/viewer/public-api.ts
diff --git a/angular-workspace/projects/ni/nimble-angular/rich-text-viewer/testing/ng-package.json b/angular-workspace/projects/ni/nimble-angular/rich-text/viewer/testing/ng-package.json
similarity index 100%
rename from angular-workspace/projects/ni/nimble-angular/rich-text-viewer/testing/ng-package.json
rename to angular-workspace/projects/ni/nimble-angular/rich-text/viewer/testing/ng-package.json
diff --git a/angular-workspace/projects/ni/nimble-angular/rich-text-viewer/testing/public-api.ts b/angular-workspace/projects/ni/nimble-angular/rich-text/viewer/testing/public-api.ts
similarity index 100%
rename from angular-workspace/projects/ni/nimble-angular/rich-text-viewer/testing/public-api.ts
rename to angular-workspace/projects/ni/nimble-angular/rich-text/viewer/testing/public-api.ts
diff --git a/angular-workspace/projects/ni/nimble-angular/rich-text-viewer/testing/rich-text-viewer.pageobject.ts b/angular-workspace/projects/ni/nimble-angular/rich-text/viewer/testing/rich-text-viewer.pageobject.ts
similarity index 100%
rename from angular-workspace/projects/ni/nimble-angular/rich-text-viewer/testing/rich-text-viewer.pageobject.ts
rename to angular-workspace/projects/ni/nimble-angular/rich-text/viewer/testing/rich-text-viewer.pageobject.ts
diff --git a/angular-workspace/projects/ni/nimble-angular/rich-text-viewer/tests/nimble-rich-text-viewer.directive.spec.ts b/angular-workspace/projects/ni/nimble-angular/rich-text/viewer/tests/nimble-rich-text-viewer.directive.spec.ts
similarity index 100%
rename from angular-workspace/projects/ni/nimble-angular/rich-text-viewer/tests/nimble-rich-text-viewer.directive.spec.ts
rename to angular-workspace/projects/ni/nimble-angular/rich-text/viewer/tests/nimble-rich-text-viewer.directive.spec.ts
diff --git a/change/@ni-nimble-angular-3a7f08ef-dd7e-4bae-8b30-937d3830d563.json b/change/@ni-nimble-angular-3a7f08ef-dd7e-4bae-8b30-937d3830d563.json
index 8edd5163fd..5681985229 100644
--- a/change/@ni-nimble-angular-3a7f08ef-dd7e-4bae-8b30-937d3830d563.json
+++ b/change/@ni-nimble-angular-3a7f08ef-dd7e-4bae-8b30-937d3830d563.json
@@ -1,6 +1,6 @@
{
"type": "patch",
- "comment": "Update folder paths for importing rich text components",
+ "comment": "Revamp folder structure for rich text components",
"packageName": "@ni/nimble-angular",
"email": "123377523+vivinkrishna-ni@users.noreply.github.com",
"dependentChangeType": "patch"
diff --git a/change/@ni-nimble-components-c9aef77f-1a41-4240-b114-67fc1d5c6c2f.json b/change/@ni-nimble-components-c9aef77f-1a41-4240-b114-67fc1d5c6c2f.json
index 9c790f46ee..a6bf4a46e8 100644
--- a/change/@ni-nimble-components-c9aef77f-1a41-4240-b114-67fc1d5c6c2f.json
+++ b/change/@ni-nimble-components-c9aef77f-1a41-4240-b114-67fc1d5c6c2f.json
@@ -1,6 +1,6 @@
{
"type": "patch",
- "comment": "Revamp rich text components folder structure",
+ "comment": "Revamp folder structure for rich text components",
"packageName": "@ni/nimble-components",
"email": "123377523+vivinkrishna-ni@users.noreply.github.com",
"dependentChangeType": "patch"
diff --git a/packages/nimble-components/src/rich-text/models/markdown-serializer.ts b/packages/nimble-components/src/rich-text/models/markdown-serializer.ts
index e6fbae74a8..48b5f65557 100644
--- a/packages/nimble-components/src/rich-text/models/markdown-serializer.ts
+++ b/packages/nimble-components/src/rich-text/models/markdown-serializer.ts
@@ -9,11 +9,7 @@ import type { Node } from 'prosemirror-model';
* Provides markdown serializer for rich text components
*/
export class RichTextMarkdownSerializer {
- private readonly markdownSerializer: MarkdownSerializer;
-
- public constructor() {
- this.markdownSerializer = this.initializeMarkdownSerializerForTipTap();
- }
+ private readonly markdownSerializer = this.initializeMarkdownSerializerForTipTap();
public serializeDOMToMarkdown(doc: Node): string {
return this.markdownSerializer.serialize(doc);
diff --git a/packages/nimble-components/src/rich-text/viewer/specs/README.md b/packages/nimble-components/src/rich-text/viewer/specs/README.md
index 2af2ffa797..f14ecb9f52 100644
--- a/packages/nimble-components/src/rich-text/viewer/specs/README.md
+++ b/packages/nimble-components/src/rich-text/viewer/specs/README.md
@@ -1,3 +1,3 @@
# Nimble Rich Text Viewer
-The spec of this component is added as part of the [`/rich-text-editor/specs/README.md`](../../rich-text-editor/specs/README.md)
+The spec of this component is added as part of the [`/rich-text/editor/specs/README.md`](../../editor/specs/README.md)
From 163de0ec83c4e9ac5850a463ba70cabd8f3deca7 Mon Sep 17 00:00:00 2001
From: Vivin Krishna <123377523+vivinkrishna-ni@users.noreply.github.com>
Date: Mon, 4 Sep 2023 13:28:59 +0530
Subject: [PATCH 16/19] Moved the specs folder to common rich-text folder
---
.../src/rich-text/{editor => }/specs/README.md | 0
.../{editor => }/specs/spec-images/button-state.png | Bin
.../specs/spec-images/editor-sample.png | Bin
.../specs/spec-images/viewer-sample.png | Bin
.../src/rich-text/viewer/specs/README.md | 3 ---
5 files changed, 3 deletions(-)
rename packages/nimble-components/src/rich-text/{editor => }/specs/README.md (100%)
rename packages/nimble-components/src/rich-text/{editor => }/specs/spec-images/button-state.png (100%)
rename packages/nimble-components/src/rich-text/{editor => }/specs/spec-images/editor-sample.png (100%)
rename packages/nimble-components/src/rich-text/{editor => }/specs/spec-images/viewer-sample.png (100%)
delete mode 100644 packages/nimble-components/src/rich-text/viewer/specs/README.md
diff --git a/packages/nimble-components/src/rich-text/editor/specs/README.md b/packages/nimble-components/src/rich-text/specs/README.md
similarity index 100%
rename from packages/nimble-components/src/rich-text/editor/specs/README.md
rename to packages/nimble-components/src/rich-text/specs/README.md
diff --git a/packages/nimble-components/src/rich-text/editor/specs/spec-images/button-state.png b/packages/nimble-components/src/rich-text/specs/spec-images/button-state.png
similarity index 100%
rename from packages/nimble-components/src/rich-text/editor/specs/spec-images/button-state.png
rename to packages/nimble-components/src/rich-text/specs/spec-images/button-state.png
diff --git a/packages/nimble-components/src/rich-text/editor/specs/spec-images/editor-sample.png b/packages/nimble-components/src/rich-text/specs/spec-images/editor-sample.png
similarity index 100%
rename from packages/nimble-components/src/rich-text/editor/specs/spec-images/editor-sample.png
rename to packages/nimble-components/src/rich-text/specs/spec-images/editor-sample.png
diff --git a/packages/nimble-components/src/rich-text/editor/specs/spec-images/viewer-sample.png b/packages/nimble-components/src/rich-text/specs/spec-images/viewer-sample.png
similarity index 100%
rename from packages/nimble-components/src/rich-text/editor/specs/spec-images/viewer-sample.png
rename to packages/nimble-components/src/rich-text/specs/spec-images/viewer-sample.png
diff --git a/packages/nimble-components/src/rich-text/viewer/specs/README.md b/packages/nimble-components/src/rich-text/viewer/specs/README.md
deleted file mode 100644
index f14ecb9f52..0000000000
--- a/packages/nimble-components/src/rich-text/viewer/specs/README.md
+++ /dev/null
@@ -1,3 +0,0 @@
-# Nimble Rich Text Viewer
-
-The spec of this component is added as part of the [`/rich-text/editor/specs/README.md`](../../editor/specs/README.md)
From c2c6367b6e675b6ddd28d2e76e20ed1d16c64315 Mon Sep 17 00:00:00 2001
From: Vivin Krishna <123377523+vivinkrishna-ni@users.noreply.github.com>
Date: Wed, 6 Sep 2023 09:42:10 +0530
Subject: [PATCH 17/19] Resolving PR comments
---
.../rich-text/editor/ng-package.json | 2 +-
.../rich-text/editor/testing/ng-package.json | 2 +-
.../rich-text/viewer/ng-package.json | 2 +-
.../rich-text/viewer/testing/ng-package.json | 2 +-
.../src/rich-text/editor/index.ts | 96 +++++++++++++++++--
.../src/rich-text/models/markdown-parser.ts | 58 -----------
.../rich-text/models/markdown-serializer.ts | 58 -----------
.../src/rich-text/viewer/index.ts | 60 +++++++++++-
8 files changed, 149 insertions(+), 131 deletions(-)
delete mode 100644 packages/nimble-components/src/rich-text/models/markdown-parser.ts
delete mode 100644 packages/nimble-components/src/rich-text/models/markdown-serializer.ts
diff --git a/angular-workspace/projects/ni/nimble-angular/rich-text/editor/ng-package.json b/angular-workspace/projects/ni/nimble-angular/rich-text/editor/ng-package.json
index 7945e60e70..e5440110fb 100644
--- a/angular-workspace/projects/ni/nimble-angular/rich-text/editor/ng-package.json
+++ b/angular-workspace/projects/ni/nimble-angular/rich-text/editor/ng-package.json
@@ -1,5 +1,5 @@
{
- "$schema": "../../../../../node_modules/ng-packagr/ng-package.schema.json",
+ "$schema": "../../../../../../node_modules/ng-packagr/ng-package.schema.json",
"lib": {
"entryFile": "public-api.ts"
}
diff --git a/angular-workspace/projects/ni/nimble-angular/rich-text/editor/testing/ng-package.json b/angular-workspace/projects/ni/nimble-angular/rich-text/editor/testing/ng-package.json
index e5440110fb..55f020bdfb 100644
--- a/angular-workspace/projects/ni/nimble-angular/rich-text/editor/testing/ng-package.json
+++ b/angular-workspace/projects/ni/nimble-angular/rich-text/editor/testing/ng-package.json
@@ -1,5 +1,5 @@
{
- "$schema": "../../../../../../node_modules/ng-packagr/ng-package.schema.json",
+ "$schema": "../../../../../../../node_modules/ng-packagr/ng-package.schema.json",
"lib": {
"entryFile": "public-api.ts"
}
diff --git a/angular-workspace/projects/ni/nimble-angular/rich-text/viewer/ng-package.json b/angular-workspace/projects/ni/nimble-angular/rich-text/viewer/ng-package.json
index 7945e60e70..e5440110fb 100644
--- a/angular-workspace/projects/ni/nimble-angular/rich-text/viewer/ng-package.json
+++ b/angular-workspace/projects/ni/nimble-angular/rich-text/viewer/ng-package.json
@@ -1,5 +1,5 @@
{
- "$schema": "../../../../../node_modules/ng-packagr/ng-package.schema.json",
+ "$schema": "../../../../../../node_modules/ng-packagr/ng-package.schema.json",
"lib": {
"entryFile": "public-api.ts"
}
diff --git a/angular-workspace/projects/ni/nimble-angular/rich-text/viewer/testing/ng-package.json b/angular-workspace/projects/ni/nimble-angular/rich-text/viewer/testing/ng-package.json
index e5440110fb..55f020bdfb 100644
--- a/angular-workspace/projects/ni/nimble-angular/rich-text/viewer/testing/ng-package.json
+++ b/angular-workspace/projects/ni/nimble-angular/rich-text/viewer/testing/ng-package.json
@@ -1,5 +1,5 @@
{
- "$schema": "../../../../../../node_modules/ng-packagr/ng-package.schema.json",
+ "$schema": "../../../../../../../node_modules/ng-packagr/ng-package.schema.json",
"lib": {
"entryFile": "public-api.ts"
}
diff --git a/packages/nimble-components/src/rich-text/editor/index.ts b/packages/nimble-components/src/rich-text/editor/index.ts
index 8b8006e957..7744f61000 100644
--- a/packages/nimble-components/src/rich-text/editor/index.ts
+++ b/packages/nimble-components/src/rich-text/editor/index.ts
@@ -13,6 +13,15 @@ import {
AnyExtension,
Extension
} from '@tiptap/core';
+import {
+ schema,
+ defaultMarkdownParser,
+ MarkdownParser,
+ MarkdownSerializer,
+ defaultMarkdownSerializer,
+ MarkdownSerializerState
+} from 'prosemirror-markdown';
+import { DOMSerializer, Node } from 'prosemirror-model';
import Bold from '@tiptap/extension-bold';
import BulletList from '@tiptap/extension-bullet-list';
import Document from '@tiptap/extension-document';
@@ -29,8 +38,6 @@ import { styles } from './styles';
import type { ToggleButton } from '../../toggle-button';
import { TipTapNodeName } from './types';
import type { ErrorPattern } from '../../patterns/error/types';
-import { RichTextMarkdownParser } from '../models/markdown-parser';
-import { RichTextMarkdownSerializer } from '../models/markdown-serializer';
declare global {
interface HTMLElementTagNameMap {
@@ -146,8 +153,9 @@ export class RichTextEditor extends FoundationElement implements ErrorPattern {
private resizeObserver?: ResizeObserver;
private updateScrollbarWidthQueued = false;
- private readonly markdownParser = new RichTextMarkdownParser();
- private readonly markdownSerializer = new RichTextMarkdownSerializer();
+ private readonly markdownParser = this.initializeMarkdownParser();
+ private readonly markdownSerializer = this.initializeMarkdownSerializer();
+ private readonly domSerializer = DOMSerializer.fromSchema(schema);
private readonly xmlSerializer = new XMLSerializer();
/**
@@ -315,9 +323,10 @@ export class RichTextEditor extends FoundationElement implements ErrorPattern {
* @public
*/
public getMarkdown(): string {
- return this.markdownSerializer.serializeDOMToMarkdown(
+ const markdownContent = this.markdownSerializer.serialize(
this.tiptapEditor.state.doc
);
+ return markdownContent;
}
/**
@@ -369,10 +378,85 @@ export class RichTextEditor extends FoundationElement implements ErrorPattern {
* This function takes the Fragment from parseMarkdownToDOM function and return the serialized string using XMLSerializer
*/
private getHtmlContent(markdown: string): string {
- const documentFragment = this.markdownParser.parseMarkdownToDOM(markdown);
+ const documentFragment = this.parseMarkdownToDOM(markdown);
return this.xmlSerializer.serializeToString(documentFragment);
}
+ private initializeMarkdownParser(): MarkdownParser {
+ /**
+ * It configures the tokenizer of the default Markdown parser with the 'zero' preset.
+ * The 'zero' preset is a configuration with no rules enabled by default to selectively enable specific rules.
+ * https://github.com/markdown-it/markdown-it/blob/2b6cac25823af011ff3bc7628bc9b06e483c5a08/lib/presets/zero.js#L1
+ *
+ */
+ const zeroTokenizerConfiguration = defaultMarkdownParser.tokenizer.configure('zero');
+
+ // The detailed information of the supported rules were provided in the below CommonMark spec document.
+ // https://spec.commonmark.org/0.30/
+ const supportedTokenizerRules = zeroTokenizerConfiguration.enable([
+ 'emphasis',
+ 'list'
+ ]);
+
+ return new MarkdownParser(
+ schema,
+ supportedTokenizerRules,
+ defaultMarkdownParser.tokens
+ );
+ }
+
+ private initializeMarkdownSerializer(): MarkdownSerializer {
+ /**
+ * orderedList Node is getting 'order' attribute which it is not present in the
+ * tip-tap orderedList Node and having start instead of order, Changed it to start (nodes.attrs.start)
+ * Assigned updated node in place of orderedList node from defaultMarkdownSerializer
+ * https://github.com/ProseMirror/prosemirror-markdown/blob/b7c1fd2fb74c7564bfe5428c7c8141ded7ebdd9f/src/to_markdown.ts#L94C2-L101C7
+ */
+ const orderedListNode = function orderedList(
+ state: MarkdownSerializerState,
+ node: Node
+ ): void {
+ const start = (node.attrs.start as number) || 1;
+ const maxW = String(start + node.childCount - 1).length;
+ const space = state.repeat(' ', maxW + 2);
+ state.renderList(node, space, i => {
+ const nStr = String(start + i);
+ return `${state.repeat(' ', maxW - nStr.length) + nStr}. `;
+ });
+ };
+
+ /**
+ * Internally Tiptap editor creates it own schema ( Nodes AND Marks ) based on the extensions ( Here Starter Kit is used for Bold, italic, orderedList and
+ * bulletList extensions) and defaultMarkdownSerializer uses schema from prosemirror-markdown to serialize the markdown.
+ * So, there is variations in the nodes and marks name (Eg. 'ordered_list' in prosemirror-markdown schema whereas 'orderedList' in tip tap editor schema),
+ * To fix up this reassigned the respective nodes and marks with tip-tap editor schema.
+ */
+ const nodes = {
+ bulletList: defaultMarkdownSerializer.nodes.bullet_list!,
+ listItem: defaultMarkdownSerializer.nodes.list_item!,
+ orderedList: orderedListNode,
+ doc: defaultMarkdownSerializer.nodes.doc!,
+ paragraph: defaultMarkdownSerializer.nodes.paragraph!,
+ text: defaultMarkdownSerializer.nodes.text!
+ };
+ const marks = {
+ italic: defaultMarkdownSerializer.marks.em!,
+ bold: defaultMarkdownSerializer.marks.strong!
+ };
+ return new MarkdownSerializer(nodes, marks);
+ }
+
+ private parseMarkdownToDOM(value: string): HTMLElement | DocumentFragment {
+ const parsedMarkdownContent = this.markdownParser.parse(value);
+ if (parsedMarkdownContent === null) {
+ return document.createDocumentFragment();
+ }
+
+ return this.domSerializer.serializeFragment(
+ parsedMarkdownContent.content
+ );
+ }
+
/**
* Binding the "transaction" event to the editor allows continuous monitoring the events and updating the button state in response to
* various actions such as mouse events, keyboard events, changes in the editor content etc,.
diff --git a/packages/nimble-components/src/rich-text/models/markdown-parser.ts b/packages/nimble-components/src/rich-text/models/markdown-parser.ts
deleted file mode 100644
index 9a1ae9a2c4..0000000000
--- a/packages/nimble-components/src/rich-text/models/markdown-parser.ts
+++ /dev/null
@@ -1,58 +0,0 @@
-import {
- schema,
- defaultMarkdownParser,
- MarkdownParser
-} from 'prosemirror-markdown';
-import { DOMSerializer } from 'prosemirror-model';
-
-/**
- * Provides markdown parser for rich text components
- */
-export class RichTextMarkdownParser {
- private readonly markdownParser: MarkdownParser;
- private readonly domSerializer: DOMSerializer;
-
- public constructor() {
- this.markdownParser = this.initializeMarkdownParser();
- this.domSerializer = DOMSerializer.fromSchema(schema);
- }
-
- /**
- * This function takes a markdown string, parses it using the ProseMirror MarkdownParser, serializes the parsed content into a
- * DOM structure using a DOMSerializer, and returns the serialized result.
- * If the markdown parser returns null, it will clear the viewer component by creating an empty document fragment.
- */
- public parseMarkdownToDOM(value: string): HTMLElement | DocumentFragment {
- const parsedMarkdownContent = this.markdownParser.parse(value);
- if (parsedMarkdownContent === null) {
- return document.createDocumentFragment();
- }
- return this.domSerializer.serializeFragment(
- parsedMarkdownContent.content
- );
- }
-
- private initializeMarkdownParser(): MarkdownParser {
- /**
- * It configures the tokenizer of the default Markdown parser with the 'zero' preset.
- * The 'zero' preset is a configuration with no rules enabled by default to selectively enable specific rules.
- * https://github.com/markdown-it/markdown-it/blob/2b6cac25823af011ff3bc7628bc9b06e483c5a08/lib/presets/zero.js#L1
- *
- */
- const zeroTokenizerConfiguration = defaultMarkdownParser.tokenizer.configure('zero');
-
- // The detailed information of the supported rules were provided in the below CommonMark spec document.
- // https://spec.commonmark.org/0.30/
- const supportedTokenizerRules = zeroTokenizerConfiguration.enable([
- 'emphasis',
- 'list',
- 'autolink'
- ]);
-
- return new MarkdownParser(
- schema,
- supportedTokenizerRules,
- defaultMarkdownParser.tokens
- );
- }
-}
diff --git a/packages/nimble-components/src/rich-text/models/markdown-serializer.ts b/packages/nimble-components/src/rich-text/models/markdown-serializer.ts
deleted file mode 100644
index 48b5f65557..0000000000
--- a/packages/nimble-components/src/rich-text/models/markdown-serializer.ts
+++ /dev/null
@@ -1,58 +0,0 @@
-import {
- MarkdownSerializer,
- defaultMarkdownSerializer,
- MarkdownSerializerState
-} from 'prosemirror-markdown';
-import type { Node } from 'prosemirror-model';
-
-/**
- * Provides markdown serializer for rich text components
- */
-export class RichTextMarkdownSerializer {
- private readonly markdownSerializer = this.initializeMarkdownSerializerForTipTap();
-
- public serializeDOMToMarkdown(doc: Node): string {
- return this.markdownSerializer.serialize(doc);
- }
-
- private initializeMarkdownSerializerForTipTap(): MarkdownSerializer {
- /**
- * orderedList Node is getting 'order' attribute which it is not present in the
- * tip-tap orderedList Node and having start instead of order, Changed it to start (nodes.attrs.start)
- * Assigned updated node in place of orderedList node from defaultMarkdownSerializer
- * https://github.com/ProseMirror/prosemirror-markdown/blob/b7c1fd2fb74c7564bfe5428c7c8141ded7ebdd9f/src/to_markdown.ts#L94C2-L101C7
- */
- const orderedListNode = function orderedList(
- state: MarkdownSerializerState,
- node: Node
- ): void {
- const start = (node.attrs.start as number) || 1;
- const maxW = String(start + node.childCount - 1).length;
- const space = state.repeat(' ', maxW + 2);
- state.renderList(node, space, i => {
- const nStr = String(start + i);
- return `${state.repeat(' ', maxW - nStr.length) + nStr}. `;
- });
- };
-
- /**
- * Internally Tiptap editor creates it own schema ( Nodes AND Marks ) based on the extensions ( Here Starter Kit is used for Bold, italic, orderedList and
- * bulletList extensions) and defaultMarkdownSerializer uses schema from prosemirror-markdown to serialize the markdown.
- * So, there is variations in the nodes and marks name (Eg. 'ordered_list' in prosemirror-markdown schema whereas 'orderedList' in tip tap editor schema),
- * To fix up this reassigned the respective nodes and marks with tip-tap editor schema.
- */
- const nodes = {
- bulletList: defaultMarkdownSerializer.nodes.bullet_list!,
- listItem: defaultMarkdownSerializer.nodes.list_item!,
- orderedList: orderedListNode,
- doc: defaultMarkdownSerializer.nodes.doc!,
- paragraph: defaultMarkdownSerializer.nodes.paragraph!,
- text: defaultMarkdownSerializer.nodes.text!
- };
- const marks = {
- italic: defaultMarkdownSerializer.marks.em!,
- bold: defaultMarkdownSerializer.marks.strong!
- };
- return new MarkdownSerializer(nodes, marks);
- }
-}
diff --git a/packages/nimble-components/src/rich-text/viewer/index.ts b/packages/nimble-components/src/rich-text/viewer/index.ts
index 6d0b86dc2d..132daa978d 100644
--- a/packages/nimble-components/src/rich-text/viewer/index.ts
+++ b/packages/nimble-components/src/rich-text/viewer/index.ts
@@ -1,8 +1,13 @@
import { DesignSystem, FoundationElement } from '@microsoft/fast-foundation';
+import {
+ schema,
+ defaultMarkdownParser,
+ MarkdownParser
+} from 'prosemirror-markdown';
+import { DOMSerializer } from 'prosemirror-model';
import { observable } from '@microsoft/fast-element';
import { template } from './template';
import { styles } from './styles';
-import { RichTextMarkdownParser } from '../models/markdown-parser';
declare global {
interface HTMLElementTagNameMap {
@@ -26,8 +31,14 @@ export class RichTextViewer extends FoundationElement {
* @internal
*/
public viewer!: HTMLDivElement;
+ private readonly markdownParser: MarkdownParser;
+ private readonly domSerializer: DOMSerializer;
- private readonly markdownParser = new RichTextMarkdownParser();
+ public constructor() {
+ super();
+ this.domSerializer = DOMSerializer.fromSchema(schema);
+ this.markdownParser = this.initializeMarkdownParser();
+ }
/**
* @internal
@@ -46,11 +57,50 @@ export class RichTextViewer extends FoundationElement {
}
}
+ private initializeMarkdownParser(): MarkdownParser {
+ /**
+ * It configures the tokenizer of the default Markdown parser with the 'zero' preset.
+ * The 'zero' preset is a configuration with no rules enabled by default to selectively enable specific rules.
+ * https://github.com/markdown-it/markdown-it/blob/2b6cac25823af011ff3bc7628bc9b06e483c5a08/lib/presets/zero.js#L1
+ *
+ */
+ const zeroTokenizerConfiguration = defaultMarkdownParser.tokenizer.configure('zero');
+
+ // The detailed information of the supported rules were provided in the below CommonMark spec document.
+ // https://spec.commonmark.org/0.30/
+ const supportedTokenizerRules = zeroTokenizerConfiguration.enable([
+ 'emphasis',
+ 'list',
+ 'autolink'
+ ]);
+
+ return new MarkdownParser(
+ schema,
+ supportedTokenizerRules,
+ defaultMarkdownParser.tokens
+ );
+ }
+
+ /**
+ *
+ * This function takes a markdown string, parses it using the ProseMirror MarkdownParser, serializes the parsed content into a
+ * DOM structure using a DOMSerializer, and returns the serialized result.
+ * If the markdown parser returns null, it will clear the viewer component by creating an empty document fragment.
+ */
+ private parseMarkdownToDOM(value: string): HTMLElement | DocumentFragment {
+ const parsedMarkdownContent = this.markdownParser.parse(value);
+ if (parsedMarkdownContent === null) {
+ return document.createDocumentFragment();
+ }
+
+ return this.domSerializer.serializeFragment(
+ parsedMarkdownContent.content
+ );
+ }
+
private updateView(): void {
if (this.markdown) {
- const serializedContent = this.markdownParser.parseMarkdownToDOM(
- this.markdown
- );
+ const serializedContent = this.parseMarkdownToDOM(this.markdown);
this.viewer.replaceChildren(serializedContent);
} else {
this.viewer.innerHTML = '';
From 6c9de89db60d70268c5ae806ade06502715615ad Mon Sep 17 00:00:00 2001
From: Vivin Krishna <123377523+vivinkrishna-ni@users.noreply.github.com>
Date: Wed, 6 Sep 2023 09:57:29 +0530
Subject: [PATCH 18/19] Minor import order in viewer spec
---
.../src/rich-text/viewer/tests/rich-text-viewer.spec.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/nimble-components/src/rich-text/viewer/tests/rich-text-viewer.spec.ts b/packages/nimble-components/src/rich-text/viewer/tests/rich-text-viewer.spec.ts
index 726feb4a93..7246dcecc4 100644
--- a/packages/nimble-components/src/rich-text/viewer/tests/rich-text-viewer.spec.ts
+++ b/packages/nimble-components/src/rich-text/viewer/tests/rich-text-viewer.spec.ts
@@ -1,9 +1,9 @@
import { html } from '@microsoft/fast-element';
+import { RichTextViewer, richTextViewerTag } from '..';
import { fixture, type Fixture } from '../../../utilities/tests/fixture';
import { RichTextViewerPageObject } from '../testing/rich-text-viewer.pageobject';
import { wackyStrings } from '../../../utilities/tests/wacky-strings';
import { getSpecTypeByNamedList } from '../../../utilities/tests/parameterized';
-import { RichTextViewer, richTextViewerTag } from '..';
async function setup(): Promise> {
return fixture(
From 065743482ee34a9ede4ced6de1770fb43d31f93e Mon Sep 17 00:00:00 2001
From: Vivin Krishna <123377523+vivinkrishna-ni@users.noreply.github.com>
Date: Wed, 6 Sep 2023 12:08:05 +0530
Subject: [PATCH 19/19] Update paths for label spec file
---
.../editor/tests/rich-text-editor-labels.spec.ts | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/packages/nimble-components/src/rich-text/editor/tests/rich-text-editor-labels.spec.ts b/packages/nimble-components/src/rich-text/editor/tests/rich-text-editor-labels.spec.ts
index 8bcd734c6b..78178af003 100644
--- a/packages/nimble-components/src/rich-text/editor/tests/rich-text-editor-labels.spec.ts
+++ b/packages/nimble-components/src/rich-text/editor/tests/rich-text-editor-labels.spec.ts
@@ -1,15 +1,15 @@
import { html } from '@microsoft/fast-element';
import { richTextEditorTag, type RichTextEditor } from '..';
-import { type Fixture, fixture } from '../../utilities/tests/fixture';
-import { themeProviderTag, type ThemeProvider } from '../../theme-provider';
+import { type Fixture, fixture } from '../../../utilities/tests/fixture';
+import { themeProviderTag, type ThemeProvider } from '../../../theme-provider';
import {
LabelProviderRichText,
labelProviderRichTextTag
-} from '../../label-provider/rich-text';
+} from '../../../label-provider/rich-text';
import { RichTextEditorPageObject } from '../testing/rich-text-editor.pageobject';
import { LabelProvider, ToolbarButton } from '../testing/types';
-import { getSpecTypeByNamedList } from '../../utilities/tests/parameterized';
-import { waitForUpdatesAsync } from '../../testing/async-helpers';
+import { getSpecTypeByNamedList } from '../../../utilities/tests/parameterized';
+import { waitForUpdatesAsync } from '../../../testing/async-helpers';
async function setup(): Promise> {
return fixture(