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/56] 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/56] 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/56] 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/56] 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/56] 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/56] 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/56] 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/56] 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/56] 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/56] 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/56] 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/56] 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/56] 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/56] 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/56] 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/56] 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/56] 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/56] 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/56] 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(
From 92df4e9a12f72e410c78e1c7a8c0a9596301767e Mon Sep 17 00:00:00 2001
From: Vivin Krishna <123377523+vivinkrishna-ni@users.noreply.github.com>
Date: Wed, 6 Sep 2023 12:32:03 +0530
Subject: [PATCH 20/56] Added markdown model files
---
.../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 +-----------
4 files changed, 127 insertions(+), 145 deletions(-)
create mode 100644 packages/nimble-components/src/rich-text/models/markdown-parser.ts
create mode 100644 packages/nimble-components/src/rich-text/models/markdown-serializer.ts
diff --git a/packages/nimble-components/src/rich-text/editor/index.ts b/packages/nimble-components/src/rich-text/editor/index.ts
index 7744f61000..8b8006e957 100644
--- a/packages/nimble-components/src/rich-text/editor/index.ts
+++ b/packages/nimble-components/src/rich-text/editor/index.ts
@@ -13,15 +13,6 @@ 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';
@@ -38,6 +29,8 @@ 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 {
@@ -153,9 +146,8 @@ export class RichTextEditor extends FoundationElement implements ErrorPattern {
private resizeObserver?: ResizeObserver;
private updateScrollbarWidthQueued = false;
- private readonly markdownParser = this.initializeMarkdownParser();
- private readonly markdownSerializer = this.initializeMarkdownSerializer();
- private readonly domSerializer = DOMSerializer.fromSchema(schema);
+ private readonly markdownParser = new RichTextMarkdownParser();
+ private readonly markdownSerializer = new RichTextMarkdownSerializer();
private readonly xmlSerializer = new XMLSerializer();
/**
@@ -323,10 +315,9 @@ export class RichTextEditor extends FoundationElement implements ErrorPattern {
* @public
*/
public getMarkdown(): string {
- const markdownContent = this.markdownSerializer.serialize(
+ return this.markdownSerializer.serializeDOMToMarkdown(
this.tiptapEditor.state.doc
);
- return markdownContent;
}
/**
@@ -378,85 +369,10 @@ 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.parseMarkdownToDOM(markdown);
+ const documentFragment = this.markdownParser.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
new file mode 100644
index 0000000000..9a1ae9a2c4
--- /dev/null
+++ b/packages/nimble-components/src/rich-text/models/markdown-parser.ts
@@ -0,0 +1,58 @@
+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
new file mode 100644
index 0000000000..48b5f65557
--- /dev/null
+++ b/packages/nimble-components/src/rich-text/models/markdown-serializer.ts
@@ -0,0 +1,58 @@
+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 132daa978d..6d0b86dc2d 100644
--- a/packages/nimble-components/src/rich-text/viewer/index.ts
+++ b/packages/nimble-components/src/rich-text/viewer/index.ts
@@ -1,13 +1,8 @@
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 {
@@ -31,14 +26,8 @@ export class RichTextViewer extends FoundationElement {
* @internal
*/
public viewer!: HTMLDivElement;
- private readonly markdownParser: MarkdownParser;
- private readonly domSerializer: DOMSerializer;
- public constructor() {
- super();
- this.domSerializer = DOMSerializer.fromSchema(schema);
- this.markdownParser = this.initializeMarkdownParser();
- }
+ private readonly markdownParser = new RichTextMarkdownParser();
/**
* @internal
@@ -57,50 +46,11 @@ 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.parseMarkdownToDOM(this.markdown);
+ const serializedContent = this.markdownParser.parseMarkdownToDOM(
+ this.markdown
+ );
this.viewer.replaceChildren(serializedContent);
} else {
this.viewer.innerHTML = '';
From 46126aa8084389a4b634bbdf74848b09a87b1fd0 Mon Sep 17 00:00:00 2001
From: Sai krishnan Perumal
Date: Wed, 6 Sep 2023 15:11:03 +0530
Subject: [PATCH 21/56] refactor: migrate markdown parser tests from text
editor and viewer component to model scripts
Signed-off-by: Sai krishnan Perumal
---
.../editor/tests/rich-text-editor.spec.ts | 323 ---------
.../models/tests/markdown-parser.spec.ts | 332 ++++++++++
.../viewer/tests/rich-text-viewer.spec.ts | 616 ------------------
3 files changed, 332 insertions(+), 939 deletions(-)
create mode 100644 packages/nimble-components/src/rich-text/models/tests/markdown-parser.spec.ts
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 4c486996a0..9e663d3ff2 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
@@ -670,329 +670,6 @@ describe('RichTextEditor', () => {
]);
});
- describe('supported rich text formatting options from markdown string to its respective HTML elements', () => {
- beforeEach(async () => {
- await connect();
- });
-
- afterEach(async () => {
- await disconnect();
- });
-
- it('bold markdown string("**") to "strong" HTML tag', () => {
- element.setMarkdown('**Bold**');
- expect(pageObject.getEditorTagNames()).toEqual(['P', 'STRONG']);
- expect(pageObject.getEditorLeafContents()).toEqual(['Bold']);
- });
-
- it('bold markdown string("__") to "strong" HTML tag', () => {
- element.setMarkdown('__Bold__');
-
- expect(pageObject.getEditorTagNames()).toEqual(['P', 'STRONG']);
- expect(pageObject.getEditorLeafContents()).toEqual(['Bold']);
- });
-
- it('italics markdown string("*") to "em" HTML tag', () => {
- element.setMarkdown('*Italics*');
-
- expect(pageObject.getEditorTagNames()).toEqual(['P', 'EM']);
- expect(pageObject.getEditorLeafContents()).toEqual(['Italics']);
- });
-
- it('italics markdown string("_") to "em" HTML tag', () => {
- element.setMarkdown('_Italics_');
-
- expect(pageObject.getEditorTagNames()).toEqual(['P', 'EM']);
- expect(pageObject.getEditorLeafContents()).toEqual(['Italics']);
- });
-
- it('numbered list markdown string("1.") to "ol" and "li" HTML tags', () => {
- element.setMarkdown('1. Numbered list');
-
- expect(pageObject.getEditorTagNames()).toEqual(['OL', 'LI', 'P']);
- expect(pageObject.getEditorLeafContents()).toEqual([
- 'Numbered list'
- ]);
- });
-
- it('numbered list markdown string("1)") to "ol" and "li" HTML tags', () => {
- element.setMarkdown('1) Numbered list');
-
- expect(pageObject.getEditorTagNames()).toEqual(['OL', 'LI', 'P']);
- expect(pageObject.getEditorLeafContents()).toEqual([
- 'Numbered list'
- ]);
- });
-
- it('multiple numbered lists markdown string("1.\n2.") to "ol" and "li" HTML tags', () => {
- element.setMarkdown('1. Option 1\n 2. Option 2');
-
- expect(pageObject.getEditorTagNames()).toEqual([
- 'OL',
- 'LI',
- 'P',
- 'LI',
- 'P'
- ]);
- expect(pageObject.getEditorLeafContents()).toEqual([
- 'Option 1',
- 'Option 2'
- ]);
- });
-
- it('multiple empty numbered lists markdown string("1.\n2.") to "ol" and "li" HTML tags', () => {
- element.setMarkdown('1. \n 2. ');
-
- expect(pageObject.getEditorTagNames()).toEqual([
- 'OL',
- 'LI',
- 'P',
- 'BR',
- 'LI',
- 'P',
- 'BR'
- ]);
- expect(pageObject.getEditorLeafContents()).toEqual(['', '']);
- });
-
- it('numbered lists that start with numbers and are not sequential to "ol" and "li" HTML tags', () => {
- element.setMarkdown('1. Option 1\n 1. Option 2');
-
- expect(pageObject.getEditorTagNames()).toEqual([
- 'OL',
- 'LI',
- 'P',
- 'LI',
- 'P'
- ]);
- expect(pageObject.getEditorLeafContents()).toEqual([
- 'Option 1',
- 'Option 2'
- ]);
- });
-
- it('numbered lists if there is some content between lists', () => {
- element.setMarkdown(
- '1. Option 1\n\nSome content in between lists\n\n 2. Option 2'
- );
-
- expect(pageObject.getEditorTagNames()).toEqual([
- 'OL',
- 'LI',
- 'P',
- 'P',
- 'OL',
- 'LI',
- 'P'
- ]);
- expect(pageObject.getEditorLeafContents()).toEqual([
- 'Option 1',
- 'Some content in between lists',
- 'Option 2'
- ]);
- });
-
- it('bulleted list markdown string("*") to "ul" and "li" HTML tags', () => {
- element.setMarkdown('* Bulleted list');
-
- expect(pageObject.getEditorTagNames()).toEqual(['UL', 'LI', 'P']);
- expect(pageObject.getEditorLeafContents()).toEqual([
- 'Bulleted list'
- ]);
- });
-
- it('bulleted list markdown string("-") to "ul" and "li" HTML tags', () => {
- element.setMarkdown('- Bulleted list');
-
- expect(pageObject.getEditorTagNames()).toEqual(['UL', 'LI', 'P']);
- expect(pageObject.getEditorLeafContents()).toEqual([
- 'Bulleted list'
- ]);
- });
-
- it('bulleted list markdown string("+") to "ul" and "li" HTML tags', () => {
- element.setMarkdown('+ Bulleted list');
-
- expect(pageObject.getEditorTagNames()).toEqual(['UL', 'LI', 'P']);
- expect(pageObject.getEditorLeafContents()).toEqual([
- 'Bulleted list'
- ]);
- });
-
- it('multiple bulleted lists markdown string("* \n* \n*") to "ul" and "li" HTML tags', () => {
- element.setMarkdown('* Option 1\n * Option 2\n * Option 3');
-
- expect(pageObject.getEditorTagNames()).toEqual([
- 'UL',
- 'LI',
- 'P',
- 'LI',
- 'P',
- 'LI',
- 'P'
- ]);
- expect(pageObject.getEditorLeafContents()).toEqual([
- 'Option 1',
- 'Option 2',
- 'Option 3'
- ]);
- });
-
- it('bulleted lists if there is some content between lists', () => {
- element.setMarkdown(
- '* Option 1\n\nSome content in between lists\n\n * Option 2'
- );
-
- expect(pageObject.getEditorTagNames()).toEqual([
- 'UL',
- 'LI',
- 'P',
- 'P',
- 'UL',
- 'LI',
- 'P'
- ]);
- expect(pageObject.getEditorLeafContents()).toEqual([
- 'Option 1',
- 'Some content in between lists',
- 'Option 2'
- ]);
- });
-
- it('numbered list with bold markdown string to "ol", "li" and "strong" HTML tags', () => {
- element.setMarkdown('1. **Numbered list in bold**');
-
- expect(pageObject.getEditorTagNames()).toEqual([
- 'OL',
- 'LI',
- 'P',
- 'STRONG'
- ]);
- expect(pageObject.getEditorLeafContents()).toEqual([
- 'Numbered list in bold'
- ]);
- });
-
- it('bulleted list with italics markdown string to "ul", "li" and "em" HTML tags', () => {
- element.setMarkdown('* *Bulleted list in italics*');
-
- expect(pageObject.getEditorTagNames()).toEqual([
- 'UL',
- 'LI',
- 'P',
- 'EM'
- ]);
- expect(pageObject.getEditorLeafContents()).toEqual([
- 'Bulleted list in italics'
- ]);
- });
-
- it('combination of all supported markdown string', () => {
- element.setMarkdown(
- '1. ***Numbered list with bold and italics***\n* ___Bulleted list with bold and italics___'
- );
-
- expect(pageObject.getEditorTagNames()).toEqual([
- 'OL',
- 'LI',
- 'P',
- 'STRONG',
- 'EM',
- 'UL',
- 'LI',
- 'P',
- 'STRONG',
- 'EM'
- ]);
- expect(pageObject.getEditorLeafContents()).toEqual([
- 'Numbered list with bold and italics',
- 'Bulleted list with bold and italics'
- ]);
- });
- });
-
- describe('various not supported markdown string values render as unchanged strings', () => {
- const notSupportedMarkdownStrings: { name: string }[] = [
- { name: '> blockquote' },
- { name: '`code`' },
- { name: '```fence```' },
- { name: '~~Strikethrough~~' },
- { name: '# Heading 1' },
- { name: '## Heading 2' },
- { name: '### Heading 3' },
- { name: '[link](url)' },
- { name: '[ref][link] [link]:url' },
- { name: '![Text](Image)' },
- { name: ' ' },
- { name: '---' },
- { name: '***' },
- { name: '___' },
- { name: '(c) (C) (r) (R) (tm) (TM) (p) (P) +-' },
- { name: '' },
- { name: 'not bold ' },
- { name: 'not italic ' },
- { name: 'not list not list ' },
- { name: '' },
- {
- name: 'https://nimble.ni.dev/ '
- },
- { name: '' }
- ];
-
- const focused: string[] = [];
- const disabled: string[] = [];
- for (const value of notSupportedMarkdownStrings) {
- const specType = getSpecTypeByNamedList(value, focused, disabled);
- specType(
- `string "${value.name}" renders as plain text "${value.name}" within paragraph tag`,
- // eslint-disable-next-line @typescript-eslint/no-loop-func
- async () => {
- element.setMarkdown(value.name);
-
- await connect();
-
- expect(pageObject.getEditorTagNames()).toEqual(['P']);
- expect(pageObject.getEditorLeafContents()).toEqual([
- value.name
- ]);
-
- await disconnect();
- }
- );
- }
- });
-
- describe('various wacky string values render as unchanged strings', () => {
- const focused: string[] = [];
- const disabled: string[] = [];
-
- wackyStrings
- .filter(value => value.name !== '\x00')
- .forEach(value => {
- const specType = getSpecTypeByNamedList(
- value,
- focused,
- disabled
- );
- 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
- async () => {
- element.setMarkdown(value.name);
-
- await connect();
-
- expect(pageObject.getEditorTagNames()).toEqual(['P']);
- expect(pageObject.getEditorLeafContents()).toEqual([
- value.name
- ]);
-
- await disconnect();
- }
- );
- });
- });
-
describe('various wacky string values modified when rendered', () => {
const focused: string[] = [];
const disabled: string[] = [];
diff --git a/packages/nimble-components/src/rich-text/models/tests/markdown-parser.spec.ts b/packages/nimble-components/src/rich-text/models/tests/markdown-parser.spec.ts
new file mode 100644
index 0000000000..73ac008cbf
--- /dev/null
+++ b/packages/nimble-components/src/rich-text/models/tests/markdown-parser.spec.ts
@@ -0,0 +1,332 @@
+import { getSpecTypeByNamedList } from '../../../utilities/tests/parameterized';
+import { wackyStrings } from '../../../utilities/tests/wacky-strings';
+import { RichTextMarkdownParser } from '../markdown-parser';
+
+describe('Markdown parser', () => {
+ let markDownParser: RichTextMarkdownParser = new RichTextMarkdownParser();
+
+ beforeEach(() => {
+ markDownParser = new RichTextMarkdownParser();
+ });
+
+ function getTagsFromDocumentFragment(doc: DocumentFragment): string[] {
+ const nodes = Array.from(doc.querySelectorAll('*')).map(
+ el => el.tagName
+ );
+ return nodes;
+ }
+
+ function getLeafContentsFromDocumentFragment(doc: DocumentFragment): string[] {
+ const nodes = Array.from(doc.querySelectorAll('*'))
+ .filter((el, _) => {
+ return el.children.length === 0;
+ })
+ .map(el => el.textContent || '');
+ return nodes;
+ }
+
+ describe('supported rich text formatting options from markdown string to its respective HTML elements', () => {
+ it('bold markdown string("**") to "strong" HTML tag', () => {
+ const doc = markDownParser.parseMarkdownToDOM('**Bold**');
+ expect(getTagsFromDocumentFragment(doc as DocumentFragment)).toEqual(['P', 'STRONG']);
+ expect(getLeafContentsFromDocumentFragment(doc as DocumentFragment)).toEqual(['Bold']);
+ });
+
+ it('bold markdown string("__") to "strong" HTML tag', () => {
+ const doc = markDownParser.parseMarkdownToDOM('__Bold__');
+
+ expect(getTagsFromDocumentFragment(doc as DocumentFragment)).toEqual(['P', 'STRONG']);
+ expect(getLeafContentsFromDocumentFragment(doc as DocumentFragment)).toEqual(['Bold']);
+ });
+
+ it('italics markdown string("*") to "em" HTML tag', () => {
+ const doc = markDownParser.parseMarkdownToDOM('*Italics*');
+
+ expect(getTagsFromDocumentFragment(doc as DocumentFragment)).toEqual(['P', 'EM']);
+ expect(getLeafContentsFromDocumentFragment(doc as DocumentFragment)).toEqual(['Italics']);
+ });
+
+ it('italics markdown string("_") to "em" HTML tag', () => {
+ const doc = markDownParser.parseMarkdownToDOM('_Italics_');
+
+ expect(getTagsFromDocumentFragment(doc as DocumentFragment)).toEqual(['P', 'EM']);
+ expect(getLeafContentsFromDocumentFragment(doc as DocumentFragment)).toEqual(['Italics']);
+ });
+
+ it('numbered list markdown string("1.") to "ol" and "li" HTML tags', () => {
+ const doc = markDownParser.parseMarkdownToDOM('1. Numbered list');
+
+ expect(getTagsFromDocumentFragment(doc as DocumentFragment)).toEqual(['OL', 'LI', 'P']);
+ expect(getLeafContentsFromDocumentFragment(doc as DocumentFragment)).toEqual([
+ 'Numbered list'
+ ]);
+ });
+
+ it('numbered list markdown string("1)") to "ol" and "li" HTML tags', () => {
+ const doc = markDownParser.parseMarkdownToDOM('1) Numbered list');
+
+ expect(getTagsFromDocumentFragment(doc as DocumentFragment)).toEqual(['OL', 'LI', 'P']);
+ expect(getLeafContentsFromDocumentFragment(doc as DocumentFragment)).toEqual([
+ 'Numbered list'
+ ]);
+ });
+
+ it('multiple numbered lists markdown string("1.\n2.") to "ol" and "li" HTML tags', () => {
+ const doc = markDownParser.parseMarkdownToDOM('1. Option 1\n 2. Option 2');
+
+ expect(getTagsFromDocumentFragment(doc as DocumentFragment)).toEqual([
+ 'OL',
+ 'LI',
+ 'P',
+ 'LI',
+ 'P'
+ ]);
+ expect(getLeafContentsFromDocumentFragment(doc as DocumentFragment)).toEqual([
+ 'Option 1',
+ 'Option 2'
+ ]);
+ });
+
+ it('multiple empty numbered lists markdown string("1.\n2.") to "ol" and "li" HTML tags', () => {
+ const doc = markDownParser.parseMarkdownToDOM('1. \n 2. ');
+
+ expect(getTagsFromDocumentFragment(doc as DocumentFragment)).toEqual([
+ 'OL',
+ 'LI',
+ 'P',
+ 'LI',
+ 'P'
+ ]);
+ expect(getLeafContentsFromDocumentFragment(doc as DocumentFragment)).toEqual(['', '']);
+ });
+
+ it('numbered lists that start with numbers and are not sequential to "ol" and "li" HTML tags', () => {
+ const doc = markDownParser.parseMarkdownToDOM('1. Option 1\n 1. Option 2');
+
+ expect(getTagsFromDocumentFragment(doc as DocumentFragment)).toEqual([
+ 'OL',
+ 'LI',
+ 'P',
+ 'LI',
+ 'P'
+ ]);
+ expect(getLeafContentsFromDocumentFragment(doc as DocumentFragment)).toEqual([
+ 'Option 1',
+ 'Option 2'
+ ]);
+ });
+
+ it('numbered lists if there is some content between lists', () => {
+ const doc = markDownParser.parseMarkdownToDOM(
+ '1. Option 1\n\nSome content in between lists\n\n 2. Option 2'
+ );
+
+ expect(getTagsFromDocumentFragment(doc as DocumentFragment)).toEqual([
+ 'OL',
+ 'LI',
+ 'P',
+ 'P',
+ 'OL',
+ 'LI',
+ 'P'
+ ]);
+ expect(getLeafContentsFromDocumentFragment(doc as DocumentFragment)).toEqual([
+ 'Option 1',
+ 'Some content in between lists',
+ 'Option 2'
+ ]);
+ });
+
+ it('bulleted list markdown string("*") to "ul" and "li" HTML tags', () => {
+ const doc = markDownParser.parseMarkdownToDOM('* Bulleted list');
+
+ expect(getTagsFromDocumentFragment(doc as DocumentFragment)).toEqual(['UL', 'LI', 'P']);
+ expect(getLeafContentsFromDocumentFragment(doc as DocumentFragment)).toEqual([
+ 'Bulleted list'
+ ]);
+ });
+
+ it('bulleted list markdown string("-") to "ul" and "li" HTML tags', () => {
+ const doc = markDownParser.parseMarkdownToDOM('- Bulleted list');
+
+ expect(getTagsFromDocumentFragment(doc as DocumentFragment)).toEqual(['UL', 'LI', 'P']);
+ expect(getLeafContentsFromDocumentFragment(doc as DocumentFragment)).toEqual([
+ 'Bulleted list'
+ ]);
+ });
+
+ it('bulleted list markdown string("+") to "ul" and "li" HTML tags', () => {
+ const doc = markDownParser.parseMarkdownToDOM('+ Bulleted list');
+
+ expect(getTagsFromDocumentFragment(doc as DocumentFragment)).toEqual(['UL', 'LI', 'P']);
+ expect(getLeafContentsFromDocumentFragment(doc as DocumentFragment)).toEqual([
+ 'Bulleted list'
+ ]);
+ });
+
+ it('multiple bulleted lists markdown string("* \n* \n*") to "ul" and "li" HTML tags', () => {
+ const doc = markDownParser.parseMarkdownToDOM('* Option 1\n * Option 2\n * Option 3');
+
+ expect(getTagsFromDocumentFragment(doc as DocumentFragment)).toEqual([
+ 'UL',
+ 'LI',
+ 'P',
+ 'LI',
+ 'P',
+ 'LI',
+ 'P'
+ ]);
+ expect(getLeafContentsFromDocumentFragment(doc as DocumentFragment)).toEqual([
+ 'Option 1',
+ 'Option 2',
+ 'Option 3'
+ ]);
+ });
+
+ it('bulleted lists if there is some content between lists', () => {
+ const doc = markDownParser.parseMarkdownToDOM(
+ '* Option 1\n\nSome content in between lists\n\n * Option 2'
+ );
+
+ expect(getTagsFromDocumentFragment(doc as DocumentFragment)).toEqual([
+ 'UL',
+ 'LI',
+ 'P',
+ 'P',
+ 'UL',
+ 'LI',
+ 'P'
+ ]);
+ expect(getLeafContentsFromDocumentFragment(doc as DocumentFragment)).toEqual([
+ 'Option 1',
+ 'Some content in between lists',
+ 'Option 2'
+ ]);
+ });
+
+ it('numbered list with bold markdown string to "ol", "li" and "strong" HTML tags', () => {
+ const doc = markDownParser.parseMarkdownToDOM('1. **Numbered list in bold**');
+
+ expect(getTagsFromDocumentFragment(doc as DocumentFragment)).toEqual([
+ 'OL',
+ 'LI',
+ 'P',
+ 'STRONG'
+ ]);
+ expect(getLeafContentsFromDocumentFragment(doc as DocumentFragment)).toEqual([
+ 'Numbered list in bold'
+ ]);
+ });
+
+ it('bulleted list with italics markdown string to "ul", "li" and "em" HTML tags', () => {
+ const doc = markDownParser.parseMarkdownToDOM('* *Bulleted list in italics*');
+
+ expect(getTagsFromDocumentFragment(doc as DocumentFragment)).toEqual([
+ 'UL',
+ 'LI',
+ 'P',
+ 'EM'
+ ]);
+ expect(getLeafContentsFromDocumentFragment(doc as DocumentFragment)).toEqual([
+ 'Bulleted list in italics'
+ ]);
+ });
+
+ it('combination of all supported markdown string', () => {
+ const doc = markDownParser.parseMarkdownToDOM(
+ '1. ***Numbered list with bold and italics***\n* ___Bulleted list with bold and italics___'
+ );
+
+ expect(getTagsFromDocumentFragment(doc as DocumentFragment)).toEqual([
+ 'OL',
+ 'LI',
+ 'P',
+ 'EM',
+ 'STRONG',
+ 'UL',
+ 'LI',
+ 'P',
+ 'EM',
+ 'STRONG'
+ ]);
+ expect(getLeafContentsFromDocumentFragment(doc as DocumentFragment)).toEqual([
+ 'Numbered list with bold and italics',
+ 'Bulleted list with bold and italics'
+ ]);
+ });
+ });
+
+ describe('various not supported markdown string values render as unchanged strings', () => {
+ const notSupportedMarkdownStrings: { name: string }[] = [
+ { name: '> blockquote' },
+ { name: '`code`' },
+ { name: '```fence```' },
+ { name: '~~Strikethrough~~' },
+ { name: '# Heading 1' },
+ { name: '## Heading 2' },
+ { name: '### Heading 3' },
+ { name: '[link](url)' },
+ { name: '[ref][link] [link]:url' },
+ { name: '![Text](Image)' },
+ { name: ' ' },
+ { name: '---' },
+ { name: '***' },
+ { name: '___' },
+ { name: '(c) (C) (r) (R) (tm) (TM) (p) (P) +-' },
+ { name: '' },
+ { name: 'not bold ' },
+ { name: 'not italic ' },
+ { name: 'not list not list ' },
+ { name: '' },
+ {
+ name: 'https://nimble.ni.dev/ '
+ },
+ { name: '' }
+ ];
+
+ const focused: string[] = [];
+ const disabled: string[] = [];
+ for (const value of notSupportedMarkdownStrings) {
+ const specType = getSpecTypeByNamedList(value, focused, disabled);
+ specType(
+ `string "${value.name}" renders as plain text "${value.name}" within paragraph tag`,
+ // eslint-disable-next-line @typescript-eslint/no-loop-func
+ () => {
+ const doc = markDownParser.parseMarkdownToDOM(value.name);
+
+ expect(getTagsFromDocumentFragment(doc as DocumentFragment)).toEqual(['P']);
+ expect(getLeafContentsFromDocumentFragment(doc as DocumentFragment)).toEqual([
+ value.name
+ ]);
+ }
+ );
+ }
+ });
+
+ describe('various wacky string values render as unchanged strings', () => {
+ const focused: string[] = [];
+ const disabled: string[] = [];
+
+ wackyStrings
+ .filter(value => value.name !== '\x00')
+ .forEach(value => {
+ const specType = getSpecTypeByNamedList(
+ value,
+ focused,
+ disabled
+ );
+ 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
+ () => {
+ const doc = markDownParser.parseMarkdownToDOM(value.name);
+
+ expect(getTagsFromDocumentFragment(doc as DocumentFragment)).toEqual(['P']);
+ expect(getLeafContentsFromDocumentFragment(doc as DocumentFragment)).toEqual([
+ value.name
+ ]);
+ }
+ );
+ });
+ });
+});
\ No newline at end of file
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 7246dcecc4..845407faf5 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
@@ -84,622 +84,6 @@ describe('RichTextViewer', () => {
await disconnect();
});
- describe('supported rich text formatting options from markdown string to its respective HTML elements', () => {
- afterEach(async () => {
- await disconnect();
- });
-
- it('bold markdown string("**") to "strong" HTML tag', async () => {
- element.markdown = '**Bold**';
-
- await connect();
-
- expect(pageObject.getRenderedMarkdownTagNames()).toEqual([
- 'P',
- 'STRONG'
- ]);
- expect(pageObject.getRenderedMarkdownLastChildContents()).toBe(
- 'Bold'
- );
- });
-
- it('bold markdown string("__") to "strong" HTML tag', async () => {
- element.markdown = '__Bold__';
-
- await connect();
-
- expect(pageObject.getRenderedMarkdownTagNames()).toEqual([
- 'P',
- 'STRONG'
- ]);
- expect(pageObject.getRenderedMarkdownLastChildContents()).toBe(
- 'Bold'
- );
- });
-
- it('italics markdown string("*") to "em" HTML tag', async () => {
- element.markdown = '*Italics*';
-
- await connect();
-
- expect(pageObject.getRenderedMarkdownTagNames()).toEqual([
- 'P',
- 'EM'
- ]);
- expect(pageObject.getRenderedMarkdownLastChildContents()).toBe(
- 'Italics'
- );
- });
-
- it('italics markdown string("_") to "em" HTML tag', async () => {
- element.markdown = '_Italics_';
-
- await connect();
-
- expect(pageObject.getRenderedMarkdownTagNames()).toEqual([
- 'P',
- 'EM'
- ]);
- expect(pageObject.getRenderedMarkdownLastChildContents()).toBe(
- 'Italics'
- );
- });
-
- it('numbered list markdown string("1.") to "ol" and "li" HTML tags', async () => {
- element.markdown = '1. Numbered list';
-
- await connect();
-
- expect(pageObject.getRenderedMarkdownTagNames()).toEqual([
- 'OL',
- 'LI',
- 'P'
- ]);
- expect(pageObject.getRenderedMarkdownLastChildContents()).toBe(
- 'Numbered list'
- );
- });
-
- it('numbered list markdown string("1)") to "ol" and "li" HTML tags', async () => {
- element.markdown = '1) Numbered list';
-
- await connect();
-
- expect(pageObject.getRenderedMarkdownTagNames()).toEqual([
- 'OL',
- 'LI',
- 'P'
- ]);
- expect(pageObject.getRenderedMarkdownLastChildContents()).toBe(
- 'Numbered list'
- );
- });
-
- it('multiple numbered lists markdown string("1.\n2.") to "ol" and "li" HTML tags', async () => {
- element.markdown = '1. Option 1\n 2. Option 2';
-
- await connect();
-
- expect(pageObject.getRenderedMarkdownTagNames()).toEqual([
- 'OL',
- 'LI',
- 'P',
- 'LI',
- 'P'
- ]);
- expect(pageObject.getRenderedMarkdownLeafContents()).toEqual([
- 'Option 1',
- 'Option 2'
- ]);
- });
-
- it('nested numbered lists markdown string to "ol" and "li" HTML tags', async () => {
- element.markdown = '1. Option 1\n\n 1. Option 2';
-
- await connect();
-
- expect(
- pageObject.getRenderedMarkdownTagNamesWithClosingTags()
- ).toEqual([
- 'OL',
- 'LI',
- 'P',
- '/P',
- 'OL',
- 'LI',
- 'P',
- '/P',
- '/LI',
- '/OL',
- '/LI',
- '/OL'
- ]);
- expect(pageObject.getRenderedMarkdownLeafContents()).toEqual([
- 'Option 1',
- 'Option 2'
- ]);
- });
-
- it('numbered lists markdown string to "ol" tag and nested bullet list markdown string to "ul" tag', async () => {
- element.markdown = '1. Option 1\n\n * Option 2';
-
- await connect();
-
- expect(
- pageObject.getRenderedMarkdownTagNamesWithClosingTags()
- ).toEqual([
- 'OL',
- 'LI',
- 'P',
- '/P',
- 'UL',
- 'LI',
- 'P',
- '/P',
- '/LI',
- '/UL',
- '/LI',
- '/OL'
- ]);
- expect(pageObject.getRenderedMarkdownLeafContents()).toEqual([
- 'Option 1',
- 'Option 2'
- ]);
- });
-
- it('sequential numbered and bulleted lists should to "ol" and once "ol" tags are closed, should have "ul" tags', async () => {
- element.markdown = '1. Option 1\n\n* Option 2';
-
- await connect();
-
- expect(
- pageObject.getRenderedMarkdownTagNamesWithClosingTags()
- ).toEqual([
- 'OL',
- 'LI',
- 'P',
- '/P',
- '/LI',
- '/OL',
- 'UL',
- 'LI',
- 'P',
- '/P',
- '/LI',
- '/UL'
- ]);
- expect(pageObject.getRenderedMarkdownLeafContents()).toEqual([
- 'Option 1',
- 'Option 2'
- ]);
- });
-
- it('multiple empty numbered lists markdown string("1.\n2.") to "ol" and "li" HTML tags', async () => {
- element.markdown = '1. \n 2. ';
-
- await connect();
-
- expect(pageObject.getRenderedMarkdownTagNames()).toEqual([
- 'OL',
- 'LI',
- 'P',
- 'LI',
- 'P'
- ]);
- expect(pageObject.getRenderedMarkdownLeafContents()).toEqual([
- '',
- ''
- ]);
- });
-
- it('numbered lists that start with numbers and are not sequential to "ol" and "li" HTML tags', async () => {
- element.markdown = '1. Option 1\n 1. Option 2';
-
- await connect();
-
- expect(pageObject.getRenderedMarkdownTagNames()).toEqual([
- 'OL',
- 'LI',
- 'P',
- 'LI',
- 'P'
- ]);
- expect(pageObject.getRenderedMarkdownLeafContents()).toEqual([
- 'Option 1',
- 'Option 2'
- ]);
- });
-
- it('numbered lists if there is some content between lists', async () => {
- element.markdown = '1. Option 1\n\nSome content in between lists\n\n 2. Option 2';
-
- await connect();
-
- expect(pageObject.getRenderedMarkdownTagNames()).toEqual([
- 'OL',
- 'LI',
- 'P',
- 'P',
- 'OL',
- 'LI',
- 'P'
- ]);
- expect(pageObject.getRenderedMarkdownLeafContents()).toEqual([
- 'Option 1',
- 'Some content in between lists',
- 'Option 2'
- ]);
- });
-
- it('bulleted list markdown string("*") to "ul" and "li" HTML tags', async () => {
- element.markdown = '* Bulleted list';
-
- await connect();
-
- expect(pageObject.getRenderedMarkdownTagNames()).toEqual([
- 'UL',
- 'LI',
- 'P'
- ]);
- expect(pageObject.getRenderedMarkdownLastChildContents()).toBe(
- 'Bulleted list'
- );
- });
-
- it('bulleted list markdown string("-") to "ul" and "li" HTML tags', async () => {
- element.markdown = '- Bulleted list';
-
- await connect();
-
- expect(pageObject.getRenderedMarkdownTagNames()).toEqual([
- 'UL',
- 'LI',
- 'P'
- ]);
- expect(pageObject.getRenderedMarkdownLastChildContents()).toBe(
- 'Bulleted list'
- );
- });
-
- it('bulleted list markdown string("+") to "ul" and "li" HTML tags', async () => {
- element.markdown = '+ Bulleted list';
-
- await connect();
-
- expect(pageObject.getRenderedMarkdownTagNames()).toEqual([
- 'UL',
- 'LI',
- 'P'
- ]);
- expect(pageObject.getRenderedMarkdownLastChildContents()).toBe(
- 'Bulleted list'
- );
- });
-
- it('nested bullet lists markdown string to "ul" and "li" HTML tags', async () => {
- element.markdown = '* Option 1\n\n * Option 2';
-
- await connect();
-
- expect(
- pageObject.getRenderedMarkdownTagNamesWithClosingTags()
- ).toEqual([
- 'UL',
- 'LI',
- 'P',
- '/P',
- 'UL',
- 'LI',
- 'P',
- '/P',
- '/LI',
- '/UL',
- '/LI',
- '/UL'
- ]);
- expect(pageObject.getRenderedMarkdownLeafContents()).toEqual([
- 'Option 1',
- 'Option 2'
- ]);
- });
-
- it('bullet lists markdown string to "ul" tag and nested numbered list markdown string to "ol" tag', async () => {
- element.markdown = '* Option 1\n\n 1. Option 2';
-
- await connect();
-
- expect(
- pageObject.getRenderedMarkdownTagNamesWithClosingTags()
- ).toEqual([
- 'UL',
- 'LI',
- 'P',
- '/P',
- 'OL',
- 'LI',
- 'P',
- '/P',
- '/LI',
- '/OL',
- '/LI',
- '/UL'
- ]);
- expect(pageObject.getRenderedMarkdownLeafContents()).toEqual([
- 'Option 1',
- 'Option 2'
- ]);
- });
-
- it('sequential bullet and numbered lists should to "ul" and once "ul" tags are closed, should have "ol" tags', async () => {
- element.markdown = '* Option 1\n\n1. Option 2';
-
- await connect();
-
- expect(
- pageObject.getRenderedMarkdownTagNamesWithClosingTags()
- ).toEqual([
- 'UL',
- 'LI',
- 'P',
- '/P',
- '/LI',
- '/UL',
- 'OL',
- 'LI',
- 'P',
- '/P',
- '/LI',
- '/OL'
- ]);
- expect(pageObject.getRenderedMarkdownLeafContents()).toEqual([
- 'Option 1',
- 'Option 2'
- ]);
- });
-
- it('multiple bulleted lists markdown string("* \n* \n*") to "ul" and "li" HTML tags', async () => {
- element.markdown = '* Option 1\n * Option 2\n * Option 3';
-
- await connect();
-
- expect(pageObject.getRenderedMarkdownTagNames()).toEqual([
- 'UL',
- 'LI',
- 'P',
- 'LI',
- 'P',
- 'LI',
- 'P'
- ]);
- expect(pageObject.getRenderedMarkdownLeafContents()).toEqual([
- 'Option 1',
- 'Option 2',
- 'Option 3'
- ]);
- });
-
- it('bulleted lists if there is some content between lists', async () => {
- element.markdown = '* Option 1\n\nSome content in between lists\n\n * Option 2';
-
- await connect();
-
- expect(pageObject.getRenderedMarkdownTagNames()).toEqual([
- 'UL',
- 'LI',
- 'P',
- 'P',
- 'UL',
- 'LI',
- 'P'
- ]);
- expect(pageObject.getRenderedMarkdownLeafContents()).toEqual([
- 'Option 1',
- 'Some content in between lists',
- 'Option 2'
- ]);
- });
-
- it('direct link markdown string to "a" tags with the link as the text content', async () => {
- element.markdown = ' ';
-
- await connect();
-
- expect(pageObject.getRenderedMarkdownTagNames()).toEqual([
- 'P',
- 'A'
- ]);
- expect(pageObject.getRenderedMarkdownLastChildContents()).toBe(
- 'https://nimble.ni.dev/'
- );
- expect(
- pageObject.getRenderedMarkdownLastChildAttribute('href')
- ).toBe('https://nimble.ni.dev/');
- });
-
- it('numbered list with bold markdown string to "ol", "li" and "strong" HTML tags', async () => {
- element.markdown = '1. **Numbered list in bold**';
-
- await connect();
-
- expect(pageObject.getRenderedMarkdownTagNames()).toEqual([
- 'OL',
- 'LI',
- 'P',
- 'STRONG'
- ]);
- expect(pageObject.getRenderedMarkdownLastChildContents()).toBe(
- 'Numbered list in bold'
- );
- });
-
- it('bulleted list with italics markdown string to "ul", "li" and "em" HTML tags', async () => {
- element.markdown = '* *Bulleted list in italics*';
-
- await connect();
-
- expect(pageObject.getRenderedMarkdownTagNames()).toEqual([
- 'UL',
- 'LI',
- 'P',
- 'EM'
- ]);
- expect(pageObject.getRenderedMarkdownLastChildContents()).toBe(
- 'Bulleted list in italics'
- );
- });
-
- it('bulleted list with direct links markdown string to "ul", "li" and "a" HTML tags', async () => {
- element.markdown = '* ';
-
- await connect();
-
- expect(pageObject.getRenderedMarkdownTagNames()).toEqual([
- 'UL',
- 'LI',
- 'P',
- 'A'
- ]);
- expect(pageObject.getRenderedMarkdownLastChildContents()).toBe(
- 'https://nimble.ni.dev/'
- );
- expect(
- pageObject.getRenderedMarkdownLastChildAttribute('href')
- ).toBe('https://nimble.ni.dev/');
- });
-
- it('direct links in bold markdown string to "strong" and "a" HTML tags', async () => {
- element.markdown = '** **';
-
- await connect();
-
- expect(pageObject.getRenderedMarkdownTagNames()).toEqual([
- 'P',
- 'STRONG',
- 'A'
- ]);
- expect(pageObject.getRenderedMarkdownLastChildContents()).toBe(
- 'https://nimble.ni.dev/'
- );
- expect(
- pageObject.getRenderedMarkdownLastChildAttribute('href')
- ).toBe('https://nimble.ni.dev/');
- });
-
- it('combination of all supported markdown string', async () => {
- element.markdown = '1. ***Numbered list with bold and italics***\n* ___Bulleted list with bold and italics___\n\n ';
-
- await connect();
-
- expect(pageObject.getRenderedMarkdownTagNames()).toEqual([
- 'OL',
- 'LI',
- 'P',
- 'EM',
- 'STRONG',
- 'UL',
- 'LI',
- 'P',
- 'EM',
- 'STRONG',
- 'P',
- 'A'
- ]);
- expect(pageObject.getRenderedMarkdownLeafContents()).toEqual([
- 'Numbered list with bold and italics',
- 'Bulleted list with bold and italics',
- 'https://nimble.ni.dev/'
- ]);
- });
- });
-
- describe('various not supported markdown string values render as unchanged strings', () => {
- const notSupportedMarkdownStrings: { name: string }[] = [
- { name: '> blockquote' },
- { name: '`code`' },
- { name: '```fence```' },
- { name: '~~Strikethrough~~' },
- { name: '# Heading 1' },
- { name: '## Heading 2' },
- { name: '### Heading 3' },
- { name: '[link](url)' },
- { name: '[ref][link] [link]:url' },
- { name: '![Text](Image)' },
- { name: ' ' },
- { name: '---' },
- { name: '***' },
- { name: '___' },
- { name: '(c) (C) (r) (R) (tm) (TM) (p) (P) +-' },
- { name: '' },
- { name: 'not bold ' },
- { name: 'not italic ' },
- { name: 'not list not list ' },
- { name: '' },
- {
- name: 'https://nimble.ni.dev/ '
- },
- { name: '' }
- ];
-
- const focused: string[] = [];
- 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
- async () => {
- element.markdown = value.name;
-
- await connect();
-
- expect(pageObject.getRenderedMarkdownTagNames()).toEqual([
- 'P'
- ]);
- expect(
- pageObject.getRenderedMarkdownLastChildContents()
- ).toBe(value.name);
-
- await disconnect();
- }
- );
- }
- });
-
- describe('various wacky string values render as unchanged strings', () => {
- const focused: string[] = [];
- const disabled: string[] = [];
-
- wackyStrings
- .filter(value => value.name !== '\x00')
- .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.name}" within paragraph tag`,
- // eslint-disable-next-line @typescript-eslint/no-loop-func
- async () => {
- element.markdown = value.name;
-
- await connect();
-
- expect(
- pageObject.getRenderedMarkdownTagNames()
- ).toEqual(['P']);
- expect(
- pageObject.getRenderedMarkdownLastChildContents()
- ).toBe(value.name);
-
- await disconnect();
- }
- );
- });
- });
-
describe('various wacky string values modified when rendered', () => {
const focused: string[] = [];
const disabled: string[] = [];
From 219941a00af07553019c42987f70ccb84d5586a0 Mon Sep 17 00:00:00 2001
From: Sai krishnan Perumal
Date: Wed, 6 Sep 2023 15:28:27 +0530
Subject: [PATCH 22/56] chore: run format
Signed-off-by: Sai krishnan Perumal
---
.../models/tests/markdown-parser.spec.ts | 274 ++++++++++--------
.../viewer/tests/rich-text-viewer.spec.ts | 1 -
2 files changed, 146 insertions(+), 129 deletions(-)
diff --git a/packages/nimble-components/src/rich-text/models/tests/markdown-parser.spec.ts b/packages/nimble-components/src/rich-text/models/tests/markdown-parser.spec.ts
index 73ac008cbf..3b42e6ed79 100644
--- a/packages/nimble-components/src/rich-text/models/tests/markdown-parser.spec.ts
+++ b/packages/nimble-components/src/rich-text/models/tests/markdown-parser.spec.ts
@@ -16,7 +16,9 @@ describe('Markdown parser', () => {
return nodes;
}
- function getLeafContentsFromDocumentFragment(doc: DocumentFragment): string[] {
+ function getLeafContentsFromDocumentFragment(
+ doc: DocumentFragment
+ ): string[] {
const nodes = Array.from(doc.querySelectorAll('*'))
.filter((el, _) => {
return el.children.length === 0;
@@ -28,92 +30,104 @@ describe('Markdown parser', () => {
describe('supported rich text formatting options from markdown string to its respective HTML elements', () => {
it('bold markdown string("**") to "strong" HTML tag', () => {
const doc = markDownParser.parseMarkdownToDOM('**Bold**');
- expect(getTagsFromDocumentFragment(doc as DocumentFragment)).toEqual(['P', 'STRONG']);
- expect(getLeafContentsFromDocumentFragment(doc as DocumentFragment)).toEqual(['Bold']);
+ expect(
+ getTagsFromDocumentFragment(doc as DocumentFragment)
+ ).toEqual(['P', 'STRONG']);
+ expect(
+ getLeafContentsFromDocumentFragment(doc as DocumentFragment)
+ ).toEqual(['Bold']);
});
it('bold markdown string("__") to "strong" HTML tag', () => {
const doc = markDownParser.parseMarkdownToDOM('__Bold__');
- expect(getTagsFromDocumentFragment(doc as DocumentFragment)).toEqual(['P', 'STRONG']);
- expect(getLeafContentsFromDocumentFragment(doc as DocumentFragment)).toEqual(['Bold']);
+ expect(
+ getTagsFromDocumentFragment(doc as DocumentFragment)
+ ).toEqual(['P', 'STRONG']);
+ expect(
+ getLeafContentsFromDocumentFragment(doc as DocumentFragment)
+ ).toEqual(['Bold']);
});
it('italics markdown string("*") to "em" HTML tag', () => {
const doc = markDownParser.parseMarkdownToDOM('*Italics*');
- expect(getTagsFromDocumentFragment(doc as DocumentFragment)).toEqual(['P', 'EM']);
- expect(getLeafContentsFromDocumentFragment(doc as DocumentFragment)).toEqual(['Italics']);
+ expect(
+ getTagsFromDocumentFragment(doc as DocumentFragment)
+ ).toEqual(['P', 'EM']);
+ expect(
+ getLeafContentsFromDocumentFragment(doc as DocumentFragment)
+ ).toEqual(['Italics']);
});
it('italics markdown string("_") to "em" HTML tag', () => {
const doc = markDownParser.parseMarkdownToDOM('_Italics_');
- expect(getTagsFromDocumentFragment(doc as DocumentFragment)).toEqual(['P', 'EM']);
- expect(getLeafContentsFromDocumentFragment(doc as DocumentFragment)).toEqual(['Italics']);
+ expect(
+ getTagsFromDocumentFragment(doc as DocumentFragment)
+ ).toEqual(['P', 'EM']);
+ expect(
+ getLeafContentsFromDocumentFragment(doc as DocumentFragment)
+ ).toEqual(['Italics']);
});
it('numbered list markdown string("1.") to "ol" and "li" HTML tags', () => {
const doc = markDownParser.parseMarkdownToDOM('1. Numbered list');
- expect(getTagsFromDocumentFragment(doc as DocumentFragment)).toEqual(['OL', 'LI', 'P']);
- expect(getLeafContentsFromDocumentFragment(doc as DocumentFragment)).toEqual([
- 'Numbered list'
- ]);
+ expect(
+ getTagsFromDocumentFragment(doc as DocumentFragment)
+ ).toEqual(['OL', 'LI', 'P']);
+ expect(
+ getLeafContentsFromDocumentFragment(doc as DocumentFragment)
+ ).toEqual(['Numbered list']);
});
it('numbered list markdown string("1)") to "ol" and "li" HTML tags', () => {
const doc = markDownParser.parseMarkdownToDOM('1) Numbered list');
- expect(getTagsFromDocumentFragment(doc as DocumentFragment)).toEqual(['OL', 'LI', 'P']);
- expect(getLeafContentsFromDocumentFragment(doc as DocumentFragment)).toEqual([
- 'Numbered list'
- ]);
+ expect(
+ getTagsFromDocumentFragment(doc as DocumentFragment)
+ ).toEqual(['OL', 'LI', 'P']);
+ expect(
+ getLeafContentsFromDocumentFragment(doc as DocumentFragment)
+ ).toEqual(['Numbered list']);
});
it('multiple numbered lists markdown string("1.\n2.") to "ol" and "li" HTML tags', () => {
- const doc = markDownParser.parseMarkdownToDOM('1. Option 1\n 2. Option 2');
+ const doc = markDownParser.parseMarkdownToDOM(
+ '1. Option 1\n 2. Option 2'
+ );
- expect(getTagsFromDocumentFragment(doc as DocumentFragment)).toEqual([
- 'OL',
- 'LI',
- 'P',
- 'LI',
- 'P'
- ]);
- expect(getLeafContentsFromDocumentFragment(doc as DocumentFragment)).toEqual([
- 'Option 1',
- 'Option 2'
- ]);
+ expect(
+ getTagsFromDocumentFragment(doc as DocumentFragment)
+ ).toEqual(['OL', 'LI', 'P', 'LI', 'P']);
+ expect(
+ getLeafContentsFromDocumentFragment(doc as DocumentFragment)
+ ).toEqual(['Option 1', 'Option 2']);
});
it('multiple empty numbered lists markdown string("1.\n2.") to "ol" and "li" HTML tags', () => {
const doc = markDownParser.parseMarkdownToDOM('1. \n 2. ');
- expect(getTagsFromDocumentFragment(doc as DocumentFragment)).toEqual([
- 'OL',
- 'LI',
- 'P',
- 'LI',
- 'P'
- ]);
- expect(getLeafContentsFromDocumentFragment(doc as DocumentFragment)).toEqual(['', '']);
+ expect(
+ getTagsFromDocumentFragment(doc as DocumentFragment)
+ ).toEqual(['OL', 'LI', 'P', 'LI', 'P']);
+ expect(
+ getLeafContentsFromDocumentFragment(doc as DocumentFragment)
+ ).toEqual(['', '']);
});
it('numbered lists that start with numbers and are not sequential to "ol" and "li" HTML tags', () => {
- const doc = markDownParser.parseMarkdownToDOM('1. Option 1\n 1. Option 2');
+ const doc = markDownParser.parseMarkdownToDOM(
+ '1. Option 1\n 1. Option 2'
+ );
- expect(getTagsFromDocumentFragment(doc as DocumentFragment)).toEqual([
- 'OL',
- 'LI',
- 'P',
- 'LI',
- 'P'
- ]);
- expect(getLeafContentsFromDocumentFragment(doc as DocumentFragment)).toEqual([
- 'Option 1',
- 'Option 2'
- ]);
+ expect(
+ getTagsFromDocumentFragment(doc as DocumentFragment)
+ ).toEqual(['OL', 'LI', 'P', 'LI', 'P']);
+ expect(
+ getLeafContentsFromDocumentFragment(doc as DocumentFragment)
+ ).toEqual(['Option 1', 'Option 2']);
});
it('numbered lists if there is some content between lists', () => {
@@ -121,16 +135,12 @@ describe('Markdown parser', () => {
'1. Option 1\n\nSome content in between lists\n\n 2. Option 2'
);
- expect(getTagsFromDocumentFragment(doc as DocumentFragment)).toEqual([
- 'OL',
- 'LI',
- 'P',
- 'P',
- 'OL',
- 'LI',
- 'P'
- ]);
- expect(getLeafContentsFromDocumentFragment(doc as DocumentFragment)).toEqual([
+ expect(
+ getTagsFromDocumentFragment(doc as DocumentFragment)
+ ).toEqual(['OL', 'LI', 'P', 'P', 'OL', 'LI', 'P']);
+ expect(
+ getLeafContentsFromDocumentFragment(doc as DocumentFragment)
+ ).toEqual([
'Option 1',
'Some content in between lists',
'Option 2'
@@ -140,47 +150,47 @@ describe('Markdown parser', () => {
it('bulleted list markdown string("*") to "ul" and "li" HTML tags', () => {
const doc = markDownParser.parseMarkdownToDOM('* Bulleted list');
- expect(getTagsFromDocumentFragment(doc as DocumentFragment)).toEqual(['UL', 'LI', 'P']);
- expect(getLeafContentsFromDocumentFragment(doc as DocumentFragment)).toEqual([
- 'Bulleted list'
- ]);
+ expect(
+ getTagsFromDocumentFragment(doc as DocumentFragment)
+ ).toEqual(['UL', 'LI', 'P']);
+ expect(
+ getLeafContentsFromDocumentFragment(doc as DocumentFragment)
+ ).toEqual(['Bulleted list']);
});
it('bulleted list markdown string("-") to "ul" and "li" HTML tags', () => {
const doc = markDownParser.parseMarkdownToDOM('- Bulleted list');
- expect(getTagsFromDocumentFragment(doc as DocumentFragment)).toEqual(['UL', 'LI', 'P']);
- expect(getLeafContentsFromDocumentFragment(doc as DocumentFragment)).toEqual([
- 'Bulleted list'
- ]);
+ expect(
+ getTagsFromDocumentFragment(doc as DocumentFragment)
+ ).toEqual(['UL', 'LI', 'P']);
+ expect(
+ getLeafContentsFromDocumentFragment(doc as DocumentFragment)
+ ).toEqual(['Bulleted list']);
});
it('bulleted list markdown string("+") to "ul" and "li" HTML tags', () => {
const doc = markDownParser.parseMarkdownToDOM('+ Bulleted list');
- expect(getTagsFromDocumentFragment(doc as DocumentFragment)).toEqual(['UL', 'LI', 'P']);
- expect(getLeafContentsFromDocumentFragment(doc as DocumentFragment)).toEqual([
- 'Bulleted list'
- ]);
+ expect(
+ getTagsFromDocumentFragment(doc as DocumentFragment)
+ ).toEqual(['UL', 'LI', 'P']);
+ expect(
+ getLeafContentsFromDocumentFragment(doc as DocumentFragment)
+ ).toEqual(['Bulleted list']);
});
it('multiple bulleted lists markdown string("* \n* \n*") to "ul" and "li" HTML tags', () => {
- const doc = markDownParser.parseMarkdownToDOM('* Option 1\n * Option 2\n * Option 3');
+ const doc = markDownParser.parseMarkdownToDOM(
+ '* Option 1\n * Option 2\n * Option 3'
+ );
- expect(getTagsFromDocumentFragment(doc as DocumentFragment)).toEqual([
- 'UL',
- 'LI',
- 'P',
- 'LI',
- 'P',
- 'LI',
- 'P'
- ]);
- expect(getLeafContentsFromDocumentFragment(doc as DocumentFragment)).toEqual([
- 'Option 1',
- 'Option 2',
- 'Option 3'
- ]);
+ expect(
+ getTagsFromDocumentFragment(doc as DocumentFragment)
+ ).toEqual(['UL', 'LI', 'P', 'LI', 'P', 'LI', 'P']);
+ expect(
+ getLeafContentsFromDocumentFragment(doc as DocumentFragment)
+ ).toEqual(['Option 1', 'Option 2', 'Option 3']);
});
it('bulleted lists if there is some content between lists', () => {
@@ -188,16 +198,12 @@ describe('Markdown parser', () => {
'* Option 1\n\nSome content in between lists\n\n * Option 2'
);
- expect(getTagsFromDocumentFragment(doc as DocumentFragment)).toEqual([
- 'UL',
- 'LI',
- 'P',
- 'P',
- 'UL',
- 'LI',
- 'P'
- ]);
- expect(getLeafContentsFromDocumentFragment(doc as DocumentFragment)).toEqual([
+ expect(
+ getTagsFromDocumentFragment(doc as DocumentFragment)
+ ).toEqual(['UL', 'LI', 'P', 'P', 'UL', 'LI', 'P']);
+ expect(
+ getLeafContentsFromDocumentFragment(doc as DocumentFragment)
+ ).toEqual([
'Option 1',
'Some content in between lists',
'Option 2'
@@ -205,31 +211,29 @@ describe('Markdown parser', () => {
});
it('numbered list with bold markdown string to "ol", "li" and "strong" HTML tags', () => {
- const doc = markDownParser.parseMarkdownToDOM('1. **Numbered list in bold**');
+ const doc = markDownParser.parseMarkdownToDOM(
+ '1. **Numbered list in bold**'
+ );
- expect(getTagsFromDocumentFragment(doc as DocumentFragment)).toEqual([
- 'OL',
- 'LI',
- 'P',
- 'STRONG'
- ]);
- expect(getLeafContentsFromDocumentFragment(doc as DocumentFragment)).toEqual([
- 'Numbered list in bold'
- ]);
+ expect(
+ getTagsFromDocumentFragment(doc as DocumentFragment)
+ ).toEqual(['OL', 'LI', 'P', 'STRONG']);
+ expect(
+ getLeafContentsFromDocumentFragment(doc as DocumentFragment)
+ ).toEqual(['Numbered list in bold']);
});
it('bulleted list with italics markdown string to "ul", "li" and "em" HTML tags', () => {
- const doc = markDownParser.parseMarkdownToDOM('* *Bulleted list in italics*');
+ const doc = markDownParser.parseMarkdownToDOM(
+ '* *Bulleted list in italics*'
+ );
- expect(getTagsFromDocumentFragment(doc as DocumentFragment)).toEqual([
- 'UL',
- 'LI',
- 'P',
- 'EM'
- ]);
- expect(getLeafContentsFromDocumentFragment(doc as DocumentFragment)).toEqual([
- 'Bulleted list in italics'
- ]);
+ expect(
+ getTagsFromDocumentFragment(doc as DocumentFragment)
+ ).toEqual(['UL', 'LI', 'P', 'EM']);
+ expect(
+ getLeafContentsFromDocumentFragment(doc as DocumentFragment)
+ ).toEqual(['Bulleted list in italics']);
});
it('combination of all supported markdown string', () => {
@@ -237,7 +241,9 @@ describe('Markdown parser', () => {
'1. ***Numbered list with bold and italics***\n* ___Bulleted list with bold and italics___'
);
- expect(getTagsFromDocumentFragment(doc as DocumentFragment)).toEqual([
+ expect(
+ getTagsFromDocumentFragment(doc as DocumentFragment)
+ ).toEqual([
'OL',
'LI',
'P',
@@ -249,7 +255,9 @@ describe('Markdown parser', () => {
'EM',
'STRONG'
]);
- expect(getLeafContentsFromDocumentFragment(doc as DocumentFragment)).toEqual([
+ expect(
+ getLeafContentsFromDocumentFragment(doc as DocumentFragment)
+ ).toEqual([
'Numbered list with bold and italics',
'Bulleted list with bold and italics'
]);
@@ -294,10 +302,14 @@ describe('Markdown parser', () => {
() => {
const doc = markDownParser.parseMarkdownToDOM(value.name);
- expect(getTagsFromDocumentFragment(doc as DocumentFragment)).toEqual(['P']);
- expect(getLeafContentsFromDocumentFragment(doc as DocumentFragment)).toEqual([
- value.name
- ]);
+ expect(
+ getTagsFromDocumentFragment(doc as DocumentFragment)
+ ).toEqual(['P']);
+ expect(
+ getLeafContentsFromDocumentFragment(
+ doc as DocumentFragment
+ )
+ ).toEqual([value.name]);
}
);
}
@@ -319,14 +331,20 @@ describe('Markdown parser', () => {
`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
() => {
- const doc = markDownParser.parseMarkdownToDOM(value.name);
-
- expect(getTagsFromDocumentFragment(doc as DocumentFragment)).toEqual(['P']);
- expect(getLeafContentsFromDocumentFragment(doc as DocumentFragment)).toEqual([
+ const doc = markDownParser.parseMarkdownToDOM(
value.name
- ]);
+ );
+
+ expect(
+ getTagsFromDocumentFragment(doc as DocumentFragment)
+ ).toEqual(['P']);
+ expect(
+ getLeafContentsFromDocumentFragment(
+ doc as DocumentFragment
+ )
+ ).toEqual([value.name]);
}
);
});
});
-});
\ No newline at end of file
+});
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 845407faf5..e5f8cc788b 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
@@ -2,7 +2,6 @@ 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';
async function setup(): Promise> {
From 5d8087823ff53ab578330197d2b1a1e5f018a821 Mon Sep 17 00:00:00 2001
From: Vivin Krishna <123377523+vivinkrishna-ni@users.noreply.github.com>
Date: Thu, 7 Sep 2023 00:46:10 +0530
Subject: [PATCH 23/56] Added spec file for serializer and updated static
classes for model files
---
.../src/rich-text/editor/index.ts | 6 +-
.../editor/tests/rich-text-editor.spec.ts | 35 ------
.../src/rich-text/models/markdown-parser.ts | 13 +--
.../rich-text/models/markdown-serializer.ts | 6 +-
.../models/tests/markdown-parser.spec.ts | 84 ++++++++++-----
.../models/tests/markdown-serializer.spec.ts | 100 ++++++++++++++++++
.../src/rich-text/viewer/index.ts | 4 +-
.../viewer/tests/rich-text-viewer.spec.ts | 39 -------
8 files changed, 168 insertions(+), 119 deletions(-)
create mode 100644 packages/nimble-components/src/rich-text/models/tests/markdown-serializer.spec.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..cfec597676 100644
--- a/packages/nimble-components/src/rich-text/editor/index.ts
+++ b/packages/nimble-components/src/rich-text/editor/index.ts
@@ -146,8 +146,6 @@ 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 xmlSerializer = new XMLSerializer();
/**
@@ -315,7 +313,7 @@ export class RichTextEditor extends FoundationElement implements ErrorPattern {
* @public
*/
public getMarkdown(): string {
- return this.markdownSerializer.serializeDOMToMarkdown(
+ return RichTextMarkdownSerializer.serializeDOMToMarkdown(
this.tiptapEditor.state.doc
);
}
@@ -369,7 +367,7 @@ 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 = RichTextMarkdownParser.parseMarkdownToDOM(markdown);
return this.xmlSerializer.serializeToString(documentFragment);
}
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 9e663d3ff2..0877f829d8 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
@@ -670,41 +670,6 @@ describe('RichTextEditor', () => {
]);
});
- describe('various wacky string values modified when rendered', () => {
- const focused: string[] = [];
- const disabled: string[] = [];
- const modifiedWackyStrings: {
- name: string,
- tags: string[],
- textContent: string[]
- }[] = [
- { name: '\0', tags: ['P'], textContent: ['�'] },
- { name: '\uFFFD', tags: ['P'], textContent: ['�'] },
- { name: '\x00', tags: ['P'], textContent: ['�'] },
- { name: '\r\r', tags: ['P', 'BR'], textContent: [''] }
- ];
-
- for (const value of modifiedWackyStrings) {
- const specType = getSpecTypeByNamedList(value, focused, disabled);
- specType(
- `wacky string "${value.name}" modified when rendered`,
- // eslint-disable-next-line @typescript-eslint/no-loop-func
- async () => {
- element.setMarkdown(value.name);
-
- await connect();
-
- expect(pageObject.getEditorTagNames()).toEqual(value.tags);
- expect(pageObject.getEditorLeafContents()).toEqual(
- value.textContent
- );
-
- await disconnect();
- }
- );
- }
- });
-
it('Should return a empty string when empty string is assigned', () => {
element.setMarkdown('markdown string');
expect(element.getMarkdown()).toBe('markdown string');
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 9a1ae9a2c4..01f71708b5 100644
--- a/packages/nimble-components/src/rich-text/models/markdown-parser.ts
+++ b/packages/nimble-components/src/rich-text/models/markdown-parser.ts
@@ -9,20 +9,15 @@ 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);
- }
+ private static readonly markdownParser = this.initializeMarkdownParser();
+ private static readonly 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 {
+ public static parseMarkdownToDOM(value: string): HTMLElement | DocumentFragment {
const parsedMarkdownContent = this.markdownParser.parse(value);
if (parsedMarkdownContent === null) {
return document.createDocumentFragment();
@@ -32,7 +27,7 @@ export class RichTextMarkdownParser {
);
}
- private initializeMarkdownParser(): MarkdownParser {
+ private static 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.
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 48b5f65557..cad1e14484 100644
--- a/packages/nimble-components/src/rich-text/models/markdown-serializer.ts
+++ b/packages/nimble-components/src/rich-text/models/markdown-serializer.ts
@@ -9,13 +9,13 @@ import type { Node } from 'prosemirror-model';
* Provides markdown serializer for rich text components
*/
export class RichTextMarkdownSerializer {
- private readonly markdownSerializer = this.initializeMarkdownSerializerForTipTap();
+ private static readonly markdownSerializer = this.initializeMarkdownSerializerForTipTap();
- public serializeDOMToMarkdown(doc: Node): string {
+ public static serializeDOMToMarkdown(doc: Node): string {
return this.markdownSerializer.serialize(doc);
}
- private initializeMarkdownSerializerForTipTap(): MarkdownSerializer {
+ private static 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)
diff --git a/packages/nimble-components/src/rich-text/models/tests/markdown-parser.spec.ts b/packages/nimble-components/src/rich-text/models/tests/markdown-parser.spec.ts
index 3b42e6ed79..f8a3f95a55 100644
--- a/packages/nimble-components/src/rich-text/models/tests/markdown-parser.spec.ts
+++ b/packages/nimble-components/src/rich-text/models/tests/markdown-parser.spec.ts
@@ -3,12 +3,6 @@ import { wackyStrings } from '../../../utilities/tests/wacky-strings';
import { RichTextMarkdownParser } from '../markdown-parser';
describe('Markdown parser', () => {
- let markDownParser: RichTextMarkdownParser = new RichTextMarkdownParser();
-
- beforeEach(() => {
- markDownParser = new RichTextMarkdownParser();
- });
-
function getTagsFromDocumentFragment(doc: DocumentFragment): string[] {
const nodes = Array.from(doc.querySelectorAll('*')).map(
el => el.tagName
@@ -29,7 +23,7 @@ describe('Markdown parser', () => {
describe('supported rich text formatting options from markdown string to its respective HTML elements', () => {
it('bold markdown string("**") to "strong" HTML tag', () => {
- const doc = markDownParser.parseMarkdownToDOM('**Bold**');
+ const doc = RichTextMarkdownParser.parseMarkdownToDOM('**Bold**');
expect(
getTagsFromDocumentFragment(doc as DocumentFragment)
).toEqual(['P', 'STRONG']);
@@ -39,7 +33,7 @@ describe('Markdown parser', () => {
});
it('bold markdown string("__") to "strong" HTML tag', () => {
- const doc = markDownParser.parseMarkdownToDOM('__Bold__');
+ const doc = RichTextMarkdownParser.parseMarkdownToDOM('__Bold__');
expect(
getTagsFromDocumentFragment(doc as DocumentFragment)
@@ -50,7 +44,7 @@ describe('Markdown parser', () => {
});
it('italics markdown string("*") to "em" HTML tag', () => {
- const doc = markDownParser.parseMarkdownToDOM('*Italics*');
+ const doc = RichTextMarkdownParser.parseMarkdownToDOM('*Italics*');
expect(
getTagsFromDocumentFragment(doc as DocumentFragment)
@@ -61,7 +55,7 @@ describe('Markdown parser', () => {
});
it('italics markdown string("_") to "em" HTML tag', () => {
- const doc = markDownParser.parseMarkdownToDOM('_Italics_');
+ const doc = RichTextMarkdownParser.parseMarkdownToDOM('_Italics_');
expect(
getTagsFromDocumentFragment(doc as DocumentFragment)
@@ -72,7 +66,7 @@ describe('Markdown parser', () => {
});
it('numbered list markdown string("1.") to "ol" and "li" HTML tags', () => {
- const doc = markDownParser.parseMarkdownToDOM('1. Numbered list');
+ const doc = RichTextMarkdownParser.parseMarkdownToDOM('1. Numbered list');
expect(
getTagsFromDocumentFragment(doc as DocumentFragment)
@@ -83,7 +77,7 @@ describe('Markdown parser', () => {
});
it('numbered list markdown string("1)") to "ol" and "li" HTML tags', () => {
- const doc = markDownParser.parseMarkdownToDOM('1) Numbered list');
+ const doc = RichTextMarkdownParser.parseMarkdownToDOM('1) Numbered list');
expect(
getTagsFromDocumentFragment(doc as DocumentFragment)
@@ -94,7 +88,7 @@ describe('Markdown parser', () => {
});
it('multiple numbered lists markdown string("1.\n2.") to "ol" and "li" HTML tags', () => {
- const doc = markDownParser.parseMarkdownToDOM(
+ const doc = RichTextMarkdownParser.parseMarkdownToDOM(
'1. Option 1\n 2. Option 2'
);
@@ -107,7 +101,7 @@ describe('Markdown parser', () => {
});
it('multiple empty numbered lists markdown string("1.\n2.") to "ol" and "li" HTML tags', () => {
- const doc = markDownParser.parseMarkdownToDOM('1. \n 2. ');
+ const doc = RichTextMarkdownParser.parseMarkdownToDOM('1. \n 2. ');
expect(
getTagsFromDocumentFragment(doc as DocumentFragment)
@@ -118,7 +112,7 @@ describe('Markdown parser', () => {
});
it('numbered lists that start with numbers and are not sequential to "ol" and "li" HTML tags', () => {
- const doc = markDownParser.parseMarkdownToDOM(
+ const doc = RichTextMarkdownParser.parseMarkdownToDOM(
'1. Option 1\n 1. Option 2'
);
@@ -131,7 +125,7 @@ describe('Markdown parser', () => {
});
it('numbered lists if there is some content between lists', () => {
- const doc = markDownParser.parseMarkdownToDOM(
+ const doc = RichTextMarkdownParser.parseMarkdownToDOM(
'1. Option 1\n\nSome content in between lists\n\n 2. Option 2'
);
@@ -148,7 +142,7 @@ describe('Markdown parser', () => {
});
it('bulleted list markdown string("*") to "ul" and "li" HTML tags', () => {
- const doc = markDownParser.parseMarkdownToDOM('* Bulleted list');
+ const doc = RichTextMarkdownParser.parseMarkdownToDOM('* Bulleted list');
expect(
getTagsFromDocumentFragment(doc as DocumentFragment)
@@ -159,7 +153,7 @@ describe('Markdown parser', () => {
});
it('bulleted list markdown string("-") to "ul" and "li" HTML tags', () => {
- const doc = markDownParser.parseMarkdownToDOM('- Bulleted list');
+ const doc = RichTextMarkdownParser.parseMarkdownToDOM('- Bulleted list');
expect(
getTagsFromDocumentFragment(doc as DocumentFragment)
@@ -170,7 +164,7 @@ describe('Markdown parser', () => {
});
it('bulleted list markdown string("+") to "ul" and "li" HTML tags', () => {
- const doc = markDownParser.parseMarkdownToDOM('+ Bulleted list');
+ const doc = RichTextMarkdownParser.parseMarkdownToDOM('+ Bulleted list');
expect(
getTagsFromDocumentFragment(doc as DocumentFragment)
@@ -181,7 +175,7 @@ describe('Markdown parser', () => {
});
it('multiple bulleted lists markdown string("* \n* \n*") to "ul" and "li" HTML tags', () => {
- const doc = markDownParser.parseMarkdownToDOM(
+ const doc = RichTextMarkdownParser.parseMarkdownToDOM(
'* Option 1\n * Option 2\n * Option 3'
);
@@ -194,7 +188,7 @@ describe('Markdown parser', () => {
});
it('bulleted lists if there is some content between lists', () => {
- const doc = markDownParser.parseMarkdownToDOM(
+ const doc = RichTextMarkdownParser.parseMarkdownToDOM(
'* Option 1\n\nSome content in between lists\n\n * Option 2'
);
@@ -211,7 +205,7 @@ describe('Markdown parser', () => {
});
it('numbered list with bold markdown string to "ol", "li" and "strong" HTML tags', () => {
- const doc = markDownParser.parseMarkdownToDOM(
+ const doc = RichTextMarkdownParser.parseMarkdownToDOM(
'1. **Numbered list in bold**'
);
@@ -224,7 +218,7 @@ describe('Markdown parser', () => {
});
it('bulleted list with italics markdown string to "ul", "li" and "em" HTML tags', () => {
- const doc = markDownParser.parseMarkdownToDOM(
+ const doc = RichTextMarkdownParser.parseMarkdownToDOM(
'* *Bulleted list in italics*'
);
@@ -237,7 +231,7 @@ describe('Markdown parser', () => {
});
it('combination of all supported markdown string', () => {
- const doc = markDownParser.parseMarkdownToDOM(
+ const doc = RichTextMarkdownParser.parseMarkdownToDOM(
'1. ***Numbered list with bold and italics***\n* ___Bulleted list with bold and italics___'
);
@@ -300,7 +294,7 @@ describe('Markdown parser', () => {
`string "${value.name}" renders as plain text "${value.name}" within paragraph tag`,
// eslint-disable-next-line @typescript-eslint/no-loop-func
() => {
- const doc = markDownParser.parseMarkdownToDOM(value.name);
+ const doc = RichTextMarkdownParser.parseMarkdownToDOM(value.name);
expect(
getTagsFromDocumentFragment(doc as DocumentFragment)
@@ -331,7 +325,7 @@ describe('Markdown parser', () => {
`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
() => {
- const doc = markDownParser.parseMarkdownToDOM(
+ const doc = RichTextMarkdownParser.parseMarkdownToDOM(
value.name
);
@@ -347,4 +341,42 @@ describe('Markdown parser', () => {
);
});
});
+
+ describe('various wacky string values modified when rendered', () => {
+ const focused: string[] = [];
+ const disabled: string[] = [];
+ const modifiedWackyStrings: {
+ name: string,
+ tags: string[],
+ textContent: string[]
+ }[] = [
+ { name: '\0', tags: ['P'], textContent: ['�'] },
+ { name: '\r\r', tags: ['P'], textContent: [''] },
+ { name: '\uFFFD', tags: ['P'], textContent: ['�'] },
+ { name: '\x00', tags: ['P'], textContent: ['�'] }
+ ];
+
+ 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
+ () => {
+ const doc = RichTextMarkdownParser.parseMarkdownToDOM(
+ value.name
+ );
+
+ expect(
+ getTagsFromDocumentFragment(doc as DocumentFragment)
+ ).toEqual(value.tags);
+ expect(
+ getLeafContentsFromDocumentFragment(
+ doc as DocumentFragment
+ )
+ ).toEqual(value.textContent);
+ }
+ );
+ }
+ });
});
diff --git a/packages/nimble-components/src/rich-text/models/tests/markdown-serializer.spec.ts b/packages/nimble-components/src/rich-text/models/tests/markdown-serializer.spec.ts
new file mode 100644
index 0000000000..c1514d6a8e
--- /dev/null
+++ b/packages/nimble-components/src/rich-text/models/tests/markdown-serializer.spec.ts
@@ -0,0 +1,100 @@
+import { Editor } from '@tiptap/core';
+import Bold from '@tiptap/extension-bold';
+import BulletList from '@tiptap/extension-bullet-list';
+import Document from '@tiptap/extension-document';
+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 type { Node } from 'prosemirror-model';
+import { RichTextMarkdownSerializer } from '../markdown-serializer';
+import { getSpecTypeByNamedList } from '../../../utilities/tests/parameterized';
+
+describe('Markdown serializer', () => {
+ const mockEditor = new Editor({
+ element: document.createElement('div'),
+ extensions: [
+ Document,
+ Paragraph,
+ Text,
+ BulletList,
+ OrderedList,
+ ListItem,
+ Bold,
+ Italic
+ ]
+ });
+
+ function getNode(htmlString: string): Node {
+ mockEditor.commands.setContent(htmlString);
+ return mockEditor.state.doc;
+ }
+
+ afterAll(() => {
+ mockEditor.destroy();
+ });
+
+ describe('various supported nodes should be serialized to a markdown output', () => {
+ const supportedNodesMarks: { name: string, html: string, markdown: string }[] = [
+ { name: 'Bold', html: 'Bold ', markdown: '**Bold**' },
+ { name: 'Italics', html: 'Italics ', markdown: '*Italics*' },
+ { name: 'Bold and Italics', html: 'Bold and Italics ', markdown: '***Bold and Italics***' },
+ { name: 'Numbered list', html: 'Numbered list
', markdown: '1. Numbered list' },
+ { name: 'Multiple numbered list', html: 'list 1
list 2
', markdown: '1. list 1\n\n2. list 2' },
+ { name: 'Numbered list with bold', html: 'Numbered list with bold
', markdown: '1. **Numbered list with bold**' },
+ { name: 'Numbered list with italics', html: 'Numbered list with italics
', markdown: '1. *Numbered list with italics*' },
+ { name: 'Bullet list', html: '', markdown: '* Bullet list' },
+ { name: 'Multiple Bullet list', html: '', markdown: '* list 1\n\n* list 2' },
+ { name: 'Bullet list with bold', html: '', markdown: '* **Bullet list with bold**' },
+ { name: 'Bullet list with italics', html: '', markdown: '* *Bullet list with italics*' }
+ ];
+
+ const focused: string[] = [];
+ const disabled: string[] = [];
+ for (const value of supportedNodesMarks) {
+ const specType = getSpecTypeByNamedList(value, focused, disabled);
+ specType(
+ `Should return ${value.name} markdown (${value.markdown}) when its respective node is passed`,
+ // eslint-disable-next-line @typescript-eslint/no-loop-func
+ () => {
+ const node = getNode(value.html);
+
+ expect(RichTextMarkdownSerializer.serializeDOMToMarkdown(node)).toBe(value.markdown);
+ }
+ );
+ }
+ });
+
+ describe('various not supported nodes should be serialized to a plain text', () => {
+ const notSupportedNodesMarks: { name: string, html: string, plainText: string }[] = [
+ { name: 'Blockquote', html: 'Blockquote
', plainText: 'Blockquote' },
+ { name: 'Code', html: 'Code
', plainText: 'Code' },
+ { name: 'CodeBlock', html: 'CodeBlock
', plainText: 'CodeBlock' },
+ { name: 'Heading', html: 'Heading ', plainText: 'Heading' },
+ { name: 'HardBreak', html: 'Hard Break
', plainText: 'Hard Break' },
+ { name: 'HorizontalRule', html: 'Horizontal
Rule', plainText: 'Horizontal\n\nRule' },
+ { name: 'Highlight', html: 'Highlight ', plainText: 'Highlight' },
+ { name: 'Link', html: 'Link ', plainText: 'Link' },
+ { name: 'Strikethrough', html: 'Strikethrough ', plainText: 'Strikethrough' },
+ { name: 'Subscript', html: 'Subscript ', plainText: 'Subscript' },
+ { name: 'Span', html: 'Span ', plainText: 'Span' },
+ { name: 'Underline', html: 'Underline ', plainText: 'Underline' }
+ ];
+
+ const focused: string[] = [];
+ const disabled: string[] = [];
+ for (const value of notSupportedNodesMarks) {
+ const specType = getSpecTypeByNamedList(value, focused, disabled);
+ specType(
+ `Should return exact node when not supported markdown (${value.name}) is passed`,
+ // eslint-disable-next-line @typescript-eslint/no-loop-func
+ () => {
+ const node = getNode(value.html);
+
+ expect(RichTextMarkdownSerializer.serializeDOMToMarkdown(node)).toBe(value.plainText);
+ }
+ );
+ }
+ });
+});
diff --git a/packages/nimble-components/src/rich-text/viewer/index.ts b/packages/nimble-components/src/rich-text/viewer/index.ts
index 6d0b86dc2d..9c809e13ae 100644
--- a/packages/nimble-components/src/rich-text/viewer/index.ts
+++ b/packages/nimble-components/src/rich-text/viewer/index.ts
@@ -27,8 +27,6 @@ export class RichTextViewer extends FoundationElement {
*/
public viewer!: HTMLDivElement;
- private readonly markdownParser = new RichTextMarkdownParser();
-
/**
* @internal
*/
@@ -48,7 +46,7 @@ export class RichTextViewer extends FoundationElement {
private updateView(): void {
if (this.markdown) {
- const serializedContent = this.markdownParser.parseMarkdownToDOM(
+ const serializedContent = RichTextMarkdownParser.parseMarkdownToDOM(
this.markdown
);
this.viewer.replaceChildren(serializedContent);
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 e5f8cc788b..763a1265ba 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
@@ -2,7 +2,6 @@ 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 { getSpecTypeByNamedList } from '../../../utilities/tests/parameterized';
async function setup(): Promise> {
return fixture(
@@ -82,42 +81,4 @@ describe('RichTextViewer', () => {
await disconnect();
});
-
- describe('various wacky string values modified when rendered', () => {
- const focused: string[] = [];
- const disabled: string[] = [];
- const modifiedWackyStrings: {
- name: string,
- tags: string[],
- textContent: string[]
- }[] = [
- { name: '\0', tags: ['P'], textContent: ['�'] },
- { name: '\r\r', tags: ['P'], textContent: [''] },
- { name: '\uFFFD', tags: ['P'], textContent: ['�'] },
- { name: '\x00', tags: ['P'], textContent: ['�'] }
- ];
-
- 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
- async () => {
- element.markdown = value.name;
-
- await connect();
-
- expect(pageObject.getRenderedMarkdownTagNames()).toEqual(
- value.tags
- );
- expect(
- pageObject.getRenderedMarkdownLeafContents()
- ).toEqual(value.textContent);
-
- await disconnect();
- }
- );
- }
- });
});
From 3a67d52ee3adc0c5fcbfe2fe54d85dcd31e8dade Mon Sep 17 00:00:00 2001
From: Vivin Krishna <123377523+vivinkrishna-ni@users.noreply.github.com>
Date: Thu, 7 Sep 2023 09:38:57 +0530
Subject: [PATCH 24/56] Fix lint
---
.../src/rich-text/models/markdown-parser.ts | 4 +-
.../models/tests/markdown-parser.spec.ts | 4 +-
.../models/tests/markdown-serializer.spec.ts | 134 +++++++++++++++---
3 files changed, 117 insertions(+), 25 deletions(-)
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 01f71708b5..e37f4045e0 100644
--- a/packages/nimble-components/src/rich-text/models/markdown-parser.ts
+++ b/packages/nimble-components/src/rich-text/models/markdown-parser.ts
@@ -17,7 +17,9 @@ export class RichTextMarkdownParser {
* 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 static parseMarkdownToDOM(value: string): HTMLElement | DocumentFragment {
+ public static parseMarkdownToDOM(
+ value: string
+ ): HTMLElement | DocumentFragment {
const parsedMarkdownContent = this.markdownParser.parse(value);
if (parsedMarkdownContent === null) {
return document.createDocumentFragment();
diff --git a/packages/nimble-components/src/rich-text/models/tests/markdown-parser.spec.ts b/packages/nimble-components/src/rich-text/models/tests/markdown-parser.spec.ts
index f8a3f95a55..fa3f274150 100644
--- a/packages/nimble-components/src/rich-text/models/tests/markdown-parser.spec.ts
+++ b/packages/nimble-components/src/rich-text/models/tests/markdown-parser.spec.ts
@@ -294,7 +294,9 @@ describe('Markdown parser', () => {
`string "${value.name}" renders as plain text "${value.name}" within paragraph tag`,
// eslint-disable-next-line @typescript-eslint/no-loop-func
() => {
- const doc = RichTextMarkdownParser.parseMarkdownToDOM(value.name);
+ const doc = RichTextMarkdownParser.parseMarkdownToDOM(
+ value.name
+ );
expect(
getTagsFromDocumentFragment(doc as DocumentFragment)
diff --git a/packages/nimble-components/src/rich-text/models/tests/markdown-serializer.spec.ts b/packages/nimble-components/src/rich-text/models/tests/markdown-serializer.spec.ts
index c1514d6a8e..161460836d 100644
--- a/packages/nimble-components/src/rich-text/models/tests/markdown-serializer.spec.ts
+++ b/packages/nimble-components/src/rich-text/models/tests/markdown-serializer.spec.ts
@@ -36,18 +36,66 @@ describe('Markdown serializer', () => {
});
describe('various supported nodes should be serialized to a markdown output', () => {
- const supportedNodesMarks: { name: string, html: string, markdown: string }[] = [
- { name: 'Bold', html: 'Bold ', markdown: '**Bold**' },
- { name: 'Italics', html: 'Italics ', markdown: '*Italics*' },
- { name: 'Bold and Italics', html: 'Bold and Italics ', markdown: '***Bold and Italics***' },
- { name: 'Numbered list', html: 'Numbered list
', markdown: '1. Numbered list' },
- { name: 'Multiple numbered list', html: 'list 1
list 2
', markdown: '1. list 1\n\n2. list 2' },
- { name: 'Numbered list with bold', html: 'Numbered list with bold
', markdown: '1. **Numbered list with bold**' },
- { name: 'Numbered list with italics', html: 'Numbered list with italics
', markdown: '1. *Numbered list with italics*' },
- { name: 'Bullet list', html: '', markdown: '* Bullet list' },
- { name: 'Multiple Bullet list', html: '', markdown: '* list 1\n\n* list 2' },
- { name: 'Bullet list with bold', html: '', markdown: '* **Bullet list with bold**' },
- { name: 'Bullet list with italics', html: '', markdown: '* *Bullet list with italics*' }
+ const supportedNodesMarks: {
+ name: string,
+ html: string,
+ markdown: string
+ }[] = [
+ {
+ name: 'Bold',
+ html: 'Bold ',
+ markdown: '**Bold**'
+ },
+ {
+ name: 'Italics',
+ html: 'Italics ',
+ markdown: '*Italics*'
+ },
+ {
+ name: 'Bold and Italics',
+ html: 'Bold and Italics ',
+ markdown: '***Bold and Italics***'
+ },
+ {
+ name: 'Numbered list',
+ html: 'Numbered list
',
+ markdown: '1. Numbered list'
+ },
+ {
+ name: 'Multiple numbered list',
+ html: 'list 1
list 2
',
+ markdown: '1. list 1\n\n2. list 2'
+ },
+ {
+ name: 'Numbered list with bold',
+ html: 'Numbered list with bold
',
+ markdown: '1. **Numbered list with bold**'
+ },
+ {
+ name: 'Numbered list with italics',
+ html: 'Numbered list with italics
',
+ markdown: '1. *Numbered list with italics*'
+ },
+ {
+ name: 'Bullet list',
+ html: '',
+ markdown: '* Bullet list'
+ },
+ {
+ name: 'Multiple Bullet list',
+ html: '',
+ markdown: '* list 1\n\n* list 2'
+ },
+ {
+ name: 'Bullet list with bold',
+ html: '',
+ markdown: '* **Bullet list with bold**'
+ },
+ {
+ name: 'Bullet list with italics',
+ html: '',
+ markdown: '* *Bullet list with italics*'
+ }
];
const focused: string[] = [];
@@ -60,26 +108,64 @@ describe('Markdown serializer', () => {
() => {
const node = getNode(value.html);
- expect(RichTextMarkdownSerializer.serializeDOMToMarkdown(node)).toBe(value.markdown);
+ expect(
+ RichTextMarkdownSerializer.serializeDOMToMarkdown(node)
+ ).toBe(value.markdown);
}
);
}
});
describe('various not supported nodes should be serialized to a plain text', () => {
- const notSupportedNodesMarks: { name: string, html: string, plainText: string }[] = [
- { name: 'Blockquote', html: 'Blockquote
', plainText: 'Blockquote' },
+ const notSupportedNodesMarks: {
+ name: string,
+ html: string,
+ plainText: string
+ }[] = [
+ {
+ name: 'Blockquote',
+ html: 'Blockquote
',
+ plainText: 'Blockquote'
+ },
{ name: 'Code', html: 'Code
', plainText: 'Code' },
- { name: 'CodeBlock', html: 'CodeBlock
', plainText: 'CodeBlock' },
+ {
+ name: 'CodeBlock',
+ html: 'CodeBlock
',
+ plainText: 'CodeBlock'
+ },
{ name: 'Heading', html: 'Heading ', plainText: 'Heading' },
- { name: 'HardBreak', html: 'Hard Break
', plainText: 'Hard Break' },
- { name: 'HorizontalRule', html: 'Horizontal
Rule', plainText: 'Horizontal\n\nRule' },
- { name: 'Highlight', html: 'Highlight ', plainText: 'Highlight' },
+ {
+ name: 'HardBreak',
+ html: 'Hard Break
',
+ plainText: 'Hard Break'
+ },
+ {
+ name: 'HorizontalRule',
+ html: 'Horizontal
Rule',
+ plainText: 'Horizontal\n\nRule'
+ },
+ {
+ name: 'Highlight',
+ html: 'Highlight ',
+ plainText: 'Highlight'
+ },
{ name: 'Link', html: 'Link ', plainText: 'Link' },
- { name: 'Strikethrough', html: 'Strikethrough ', plainText: 'Strikethrough' },
- { name: 'Subscript', html: 'Subscript ', plainText: 'Subscript' },
+ {
+ name: 'Strikethrough',
+ html: 'Strikethrough ',
+ plainText: 'Strikethrough'
+ },
+ {
+ name: 'Subscript',
+ html: 'Subscript ',
+ plainText: 'Subscript'
+ },
{ name: 'Span', html: 'Span ', plainText: 'Span' },
- { name: 'Underline', html: 'Underline ', plainText: 'Underline' }
+ {
+ name: 'Underline',
+ html: 'Underline ',
+ plainText: 'Underline'
+ }
];
const focused: string[] = [];
@@ -92,7 +178,9 @@ describe('Markdown serializer', () => {
() => {
const node = getNode(value.html);
- expect(RichTextMarkdownSerializer.serializeDOMToMarkdown(node)).toBe(value.plainText);
+ expect(
+ RichTextMarkdownSerializer.serializeDOMToMarkdown(node)
+ ).toBe(value.plainText);
}
);
}
From 5beb94009c19ff4cf0787a6b24d3f9ecc89d5c9b Mon Sep 17 00:00:00 2001
From: Vivin Krishna <123377523+vivinkrishna-ni@users.noreply.github.com>
Date: Thu, 7 Sep 2023 09:40:48 +0530
Subject: [PATCH 25/56] Removed previous branch change files
---
...imble-angular-3a7f08ef-dd7e-4bae-8b30-937d3830d563.json | 7 -------
...le-components-c9aef77f-1a41-4240-b114-67fc1d5c6c2f.json | 7 -------
2 files changed, 14 deletions(-)
delete mode 100644 change/@ni-nimble-angular-3a7f08ef-dd7e-4bae-8b30-937d3830d563.json
delete 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
deleted file mode 100644
index 5681985229..0000000000
--- a/change/@ni-nimble-angular-3a7f08ef-dd7e-4bae-8b30-937d3830d563.json
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "type": "patch",
- "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
deleted file mode 100644
index a6bf4a46e8..0000000000
--- a/change/@ni-nimble-components-c9aef77f-1a41-4240-b114-67fc1d5c6c2f.json
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "type": "patch",
- "comment": "Revamp folder structure for rich text components",
- "packageName": "@ni/nimble-components",
- "email": "123377523+vivinkrishna-ni@users.noreply.github.com",
- "dependentChangeType": "patch"
-}
From a6490b95b165de4dd8e5d3fa97f7af8f8899ae0b Mon Sep 17 00:00:00 2001
From: Sai krishnan Perumal
Date: Thu, 7 Sep 2023 10:16:20 +0530
Subject: [PATCH 26/56] Change files
---
...le-components-21de2fc8-dfc6-4249-9098-d7842ce0eff3.json | 7 +++++++
1 file changed, 7 insertions(+)
create mode 100644 change/@ni-nimble-components-21de2fc8-dfc6-4249-9098-d7842ce0eff3.json
diff --git a/change/@ni-nimble-components-21de2fc8-dfc6-4249-9098-d7842ce0eff3.json b/change/@ni-nimble-components-21de2fc8-dfc6-4249-9098-d7842ce0eff3.json
new file mode 100644
index 0000000000..0022132e06
--- /dev/null
+++ b/change/@ni-nimble-components-21de2fc8-dfc6-4249-9098-d7842ce0eff3.json
@@ -0,0 +1,7 @@
+{
+ "type": "none",
+ "comment": "Refactored rich text components to use common models",
+ "packageName": "@ni/nimble-components",
+ "email": "sai.krishnan.perumal@ni.com",
+ "dependentChangeType": "none"
+}
From 7664716b368b9717746965b8252b12e732a53788 Mon Sep 17 00:00:00 2001
From: Sai krishnan Perumal <123591928+saikrishnan-ni@users.noreply.github.com>
Date: Thu, 7 Sep 2023 12:27:07 +0530
Subject: [PATCH 27/56] refactor: update email id in change file
Signed-off-by: Sai krishnan Perumal <123591928+saikrishnan-ni@users.noreply.github.com>
---
...-nimble-components-21de2fc8-dfc6-4249-9098-d7842ce0eff3.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/change/@ni-nimble-components-21de2fc8-dfc6-4249-9098-d7842ce0eff3.json b/change/@ni-nimble-components-21de2fc8-dfc6-4249-9098-d7842ce0eff3.json
index 0022132e06..7fbe260da7 100644
--- a/change/@ni-nimble-components-21de2fc8-dfc6-4249-9098-d7842ce0eff3.json
+++ b/change/@ni-nimble-components-21de2fc8-dfc6-4249-9098-d7842ce0eff3.json
@@ -2,6 +2,6 @@
"type": "none",
"comment": "Refactored rich text components to use common models",
"packageName": "@ni/nimble-components",
- "email": "sai.krishnan.perumal@ni.com",
+ "email": "123591928+saikrishnan-ni@users.noreply.github.com",
"dependentChangeType": "none"
}
From f674ddf0e43f443740b3af9143da19fdf530a125 Mon Sep 17 00:00:00 2001
From: Sai krishnan Perumal <123591928+saikrishnan-ni@users.noreply.github.com>
Date: Thu, 7 Sep 2023 13:19:58 +0530
Subject: [PATCH 28/56] refactor: remove redundant eslint disable
Signed-off-by: Sai krishnan Perumal <123591928+saikrishnan-ni@users.noreply.github.com>
---
.../src/rich-text/models/tests/markdown-parser.spec.ts | 1 -
1 file changed, 1 deletion(-)
diff --git a/packages/nimble-components/src/rich-text/models/tests/markdown-parser.spec.ts b/packages/nimble-components/src/rich-text/models/tests/markdown-parser.spec.ts
index fa3f274150..72613e6b45 100644
--- a/packages/nimble-components/src/rich-text/models/tests/markdown-parser.spec.ts
+++ b/packages/nimble-components/src/rich-text/models/tests/markdown-parser.spec.ts
@@ -360,7 +360,6 @@ describe('Markdown parser', () => {
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
From 6109d1f0534439067a7f1c043603e37375c3eb6f Mon Sep 17 00:00:00 2001
From: Sai krishnan Perumal <123591928+saikrishnan-ni@users.noreply.github.com>
Date: Thu, 7 Sep 2023 16:29:26 +0530
Subject: [PATCH 29/56] refactor: add in review suggestion test cases
Signed-off-by: Sai krishnan Perumal <123591928+saikrishnan-ni@users.noreply.github.com>
---
.../models/tests/markdown-serializer.spec.ts | 92 ++++++++++++++++---
1 file changed, 81 insertions(+), 11 deletions(-)
diff --git a/packages/nimble-components/src/rich-text/models/tests/markdown-serializer.spec.ts b/packages/nimble-components/src/rich-text/models/tests/markdown-serializer.spec.ts
index 161460836d..3ef28c8a56 100644
--- a/packages/nimble-components/src/rich-text/models/tests/markdown-serializer.spec.ts
+++ b/packages/nimble-components/src/rich-text/models/tests/markdown-serializer.spec.ts
@@ -77,25 +77,85 @@ describe('Markdown serializer', () => {
markdown: '1. *Numbered list with italics*'
},
{
- name: 'Bullet list',
- html: '',
- markdown: '* Bullet list'
+ name: 'Bulleted list',
+ html: '',
+ markdown: '* Bulleted list'
},
{
- name: 'Multiple Bullet list',
+ name: 'Multiple Bulleted list',
html: '',
markdown: '* list 1\n\n* list 2'
},
{
- name: 'Bullet list with bold',
- html: '',
- markdown: '* **Bullet list with bold**'
+ name: 'Bulleted list with bold',
+ html: '',
+ markdown: '* **Bulleted list with bold**'
},
{
- name: 'Bullet list with italics',
- html: '',
- markdown: '* *Bullet list with italics*'
- }
+ name: 'Bulleted list with italics',
+ html: '',
+ markdown: '* *Bulleted list with italics*'
+ },
+ {
+ name: 'Nested list with levels 1 - Bulleted list, 2 - Numbered list (Bold)',
+ html: 'Bulleted list
Nested bold numbered list
',
+ markdown: '* Bulleted list\n\n 1. **Nested bold numbered list**'
+ },
+ {
+ name: 'Nested list with levels 1 - Bulleted list, 2 - Numbered list (Italics)',
+ html: 'Bulleted list
Nested bold numbered list
',
+ markdown: '* Bulleted list\n\n 1. *Nested bold numbered list*'
+ },
+ {
+ name: 'Nested list with levels 1- Numbered list (Bold), 2-Bulleted list',
+ html: 'Numbered list bold
',
+ markdown: '1. **Numbered list bold**\n\n * Nested bulleted list'
+ },
+ {
+ name: 'Nested list with levels 1- Numbered list (Italics), 2-Bulleted list',
+ html: 'Numbered list italics
',
+ markdown: '1. *Numbered list italics*\n\n * Nested bulleted list'
+ },
+ {
+ name: 'Nested list with levels 1- Numbered list, level 2- Bulleted list with multiple items',
+ html: 'Numbered list
',
+ markdown: '1. Numbered list\n\n * list 1\n\n * list 2'
+ },
+ {
+ name: 'Nested list with levels 1- Bulleted list, level 2- Numbered list with multiple items',
+ html: 'Bulleted list
list 1
list 2
',
+ markdown: '* Bulleted list\n\n 1. list 1\n\n 2. list 2'
+ },
+ {
+ name: 'HTML entities <&>',
+ html: '&',
+ markdown: '&'
+ },
+ {
+ name: 'HTML entities <™>',
+ html: '™',
+ markdown: '™'
+ },
+ {
+ name: 'HTML entities <&euro>',
+ html: '€',
+ markdown: '€'
+ },
+ {
+ name: 'Markdown syntax strings <*>',
+ html: '*',
+ markdown: '\\*'
+ },
+ {
+ name: 'Markdown syntax strings <**>',
+ html: '**',
+ markdown: '\\*\\*'
+ },
+ {
+ name: 'Markdown syntax strings <_>',
+ html: '_',
+ markdown: '\\_'
+ },
];
const focused: string[] = [];
@@ -165,6 +225,16 @@ describe('Markdown serializer', () => {
name: 'Underline',
html: 'Underline ',
plainText: 'Underline'
+ },
+ {
+ name: 'Script tag',
+ html: '',
+ plainText: ''
+ },
+ {
+ name: 'iframe tag',
+ html: '',
+ plainText: ''
}
];
From 81e348a975766cb432ef52fdae3b34a791e99131 Mon Sep 17 00:00:00 2001
From: Sai krishnan Perumal <123591928+saikrishnan-ni@users.noreply.github.com>
Date: Thu, 7 Sep 2023 17:00:06 +0530
Subject: [PATCH 30/56] chore: run format
Signed-off-by: Sai krishnan Perumal <123591928+saikrishnan-ni@users.noreply.github.com>
---
.../models/tests/markdown-serializer.spec.ts | 15 +++++++++------
1 file changed, 9 insertions(+), 6 deletions(-)
diff --git a/packages/nimble-components/src/rich-text/models/tests/markdown-serializer.spec.ts b/packages/nimble-components/src/rich-text/models/tests/markdown-serializer.spec.ts
index 3ef28c8a56..5f2c39529a 100644
--- a/packages/nimble-components/src/rich-text/models/tests/markdown-serializer.spec.ts
+++ b/packages/nimble-components/src/rich-text/models/tests/markdown-serializer.spec.ts
@@ -99,7 +99,8 @@ describe('Markdown serializer', () => {
{
name: 'Nested list with levels 1 - Bulleted list, 2 - Numbered list (Bold)',
html: 'Bulleted list
Nested bold numbered list
',
- markdown: '* Bulleted list\n\n 1. **Nested bold numbered list**'
+ markdown:
+ '* Bulleted list\n\n 1. **Nested bold numbered list**'
},
{
name: 'Nested list with levels 1 - Bulleted list, 2 - Numbered list (Italics)',
@@ -109,12 +110,14 @@ describe('Markdown serializer', () => {
{
name: 'Nested list with levels 1- Numbered list (Bold), 2-Bulleted list',
html: 'Numbered list bold
',
- markdown: '1. **Numbered list bold**\n\n * Nested bulleted list'
+ markdown:
+ '1. **Numbered list bold**\n\n * Nested bulleted list'
},
{
name: 'Nested list with levels 1- Numbered list (Italics), 2-Bulleted list',
html: 'Numbered list italics
',
- markdown: '1. *Numbered list italics*\n\n * Nested bulleted list'
+ markdown:
+ '1. *Numbered list italics*\n\n * Nested bulleted list'
},
{
name: 'Nested list with levels 1- Numbered list, level 2- Bulleted list with multiple items',
@@ -155,7 +158,7 @@ describe('Markdown serializer', () => {
name: 'Markdown syntax strings <_>',
html: '_',
markdown: '\\_'
- },
+ }
];
const focused: string[] = [];
@@ -228,12 +231,12 @@ describe('Markdown serializer', () => {
},
{
name: 'Script tag',
- html: '',
+ html: '',
plainText: ''
},
{
name: 'iframe tag',
- html: '',
+ html: '',
plainText: ''
}
];
From 1a6e97f9fe69283908eeb4264df530649b07ce21 Mon Sep 17 00:00:00 2001
From: Sai krishnan Perumal <123591928+saikrishnan-ni@users.noreply.github.com>
Date: Thu, 7 Sep 2023 17:41:34 +0530
Subject: [PATCH 31/56] refactor: add in review changes
Signed-off-by: Sai krishnan Perumal <123591928+saikrishnan-ni@users.noreply.github.com>
---
.../rich-text/models/tests/markdown-serializer.spec.ts | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/packages/nimble-components/src/rich-text/models/tests/markdown-serializer.spec.ts b/packages/nimble-components/src/rich-text/models/tests/markdown-serializer.spec.ts
index 5f2c39529a..1c60d696c0 100644
--- a/packages/nimble-components/src/rich-text/models/tests/markdown-serializer.spec.ts
+++ b/packages/nimble-components/src/rich-text/models/tests/markdown-serializer.spec.ts
@@ -131,7 +131,7 @@ describe('Markdown serializer', () => {
},
{
name: 'HTML entities <&>',
- html: '&',
+ html: '&',
markdown: '&'
},
{
@@ -147,17 +147,17 @@ describe('Markdown serializer', () => {
{
name: 'Markdown syntax strings <*>',
html: '*',
- markdown: '\\*'
+ markdown: String.raw`\*`
},
{
name: 'Markdown syntax strings <**>',
html: '**',
- markdown: '\\*\\*'
+ markdown: String.raw`\*\*`
},
{
name: 'Markdown syntax strings <_>',
html: '_',
- markdown: '\\_'
+ markdown: String.raw`\_`
}
];
From 7bedc9c705a5945dce2ae7d879aed56babdb292d Mon Sep 17 00:00:00 2001
From: Vivin Krishna <123377523+vivinkrishna-ni@users.noreply.github.com>
Date: Thu, 7 Sep 2023 19:45:01 +0530
Subject: [PATCH 32/56] absolute link support in editor
---
package-lock.json | 22 ++
packages/nimble-components/package.json | 1 +
.../src/rich-text/editor/index.ts | 42 ++-
.../src/rich-text/editor/styles.ts | 23 ++
.../testing/rich-text-editor.pageobject.ts | 41 ++-
.../tests/rich-text-editor-matrix.stories.ts | 4 +-
.../editor/tests/rich-text-editor.spec.ts | 341 ++++++++++++++++++
.../editor/tests/rich-text-editor.stories.ts | 15 +-
.../src/rich-text/models/markdown-parser.ts | 39 +-
.../rich-text/models/markdown-serializer.ts | 10 +-
.../models/tests/markdown-parser.spec.ts | 204 ++++++++++-
.../models/tests/markdown-serializer.spec.ts | 37 +-
.../src/rich-text/viewer/styles.ts | 16 +-
13 files changed, 751 insertions(+), 44 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index 7e7034d708..4721a15fe3 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10705,6 +10705,22 @@
"@tiptap/core": "^2.0.0"
}
},
+ "node_modules/@tiptap/extension-link": {
+ "version": "2.1.7",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-link/-/extension-link-2.1.7.tgz",
+ "integrity": "sha512-NDfoMCkThng1B530pMg5y69+eWoghZXK2uCntrJH7Rs8jNeGMyt9wGIOd7N8ZYz0oJ2ZYKzZjS0RANdBDS17DA==",
+ "dependencies": {
+ "linkifyjs": "^4.1.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/ueberdosis"
+ },
+ "peerDependencies": {
+ "@tiptap/core": "^2.0.0",
+ "@tiptap/pm": "^2.0.0"
+ }
+ },
"node_modules/@tiptap/extension-list-item": {
"version": "2.1.6",
"resolved": "https://registry.npmjs.org/@tiptap/extension-list-item/-/extension-list-item-2.1.6.tgz",
@@ -22663,6 +22679,11 @@
"uc.micro": "^1.0.1"
}
},
+ "node_modules/linkifyjs": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/linkifyjs/-/linkifyjs-4.1.1.tgz",
+ "integrity": "sha512-zFN/CTVmbcVef+WaDXT63dNzzkfRBKT1j464NJQkV7iSgJU0sLBus9W0HBwnXK13/hf168pbrx/V/bjEHOXNHA=="
+ },
"node_modules/liquidjs": {
"version": "9.43.0",
"resolved": "https://registry.npmjs.org/liquidjs/-/liquidjs-9.43.0.tgz",
@@ -34734,6 +34755,7 @@
"@tiptap/extension-document": "^2.1.6",
"@tiptap/extension-history": "^2.1.6",
"@tiptap/extension-italic": "^2.1.6",
+ "@tiptap/extension-link": "^2.1.6",
"@tiptap/extension-list-item": "^2.1.6",
"@tiptap/extension-ordered-list": "^2.1.6",
"@tiptap/extension-paragraph": "^2.1.6",
diff --git a/packages/nimble-components/package.json b/packages/nimble-components/package.json
index 5c4d6b1d23..8a67925132 100644
--- a/packages/nimble-components/package.json
+++ b/packages/nimble-components/package.json
@@ -70,6 +70,7 @@
"@tiptap/extension-document": "^2.1.6",
"@tiptap/extension-history": "^2.1.6",
"@tiptap/extension-italic": "^2.1.6",
+ "@tiptap/extension-link": "^2.1.6",
"@tiptap/extension-list-item": "^2.1.6",
"@tiptap/extension-ordered-list": "^2.1.6",
"@tiptap/extension-paragraph": "^2.1.6",
diff --git a/packages/nimble-components/src/rich-text/editor/index.ts b/packages/nimble-components/src/rich-text/editor/index.ts
index d32f23b716..dceadbd357 100644
--- a/packages/nimble-components/src/rich-text/editor/index.ts
+++ b/packages/nimble-components/src/rich-text/editor/index.ts
@@ -11,13 +11,15 @@ import {
findParentNode,
isList,
AnyExtension,
- Extension
+ Extension,
+ Mark
} 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 Link, { LinkOptions } from '@tiptap/extension-link';
import ListItem from '@tiptap/extension-list-item';
import OrderedList from '@tiptap/extension-ordered-list';
import Paragraph from '@tiptap/extension-paragraph';
@@ -31,6 +33,7 @@ import { TipTapNodeName } from './types';
import type { ErrorPattern } from '../../patterns/error/types';
import { RichTextMarkdownParser } from '../models/markdown-parser';
import { RichTextMarkdownSerializer } from '../models/markdown-serializer';
+import { anchorTag } from '../../anchor';
declare global {
interface HTMLElementTagNameMap {
@@ -330,6 +333,8 @@ export class RichTextEditor extends FoundationElement implements ErrorPattern {
}
private createTiptapEditor(): Editor {
+ const customLink = this.getCustomLinkExtension();
+
/**
* For more information on the extensions for the supported formatting options, refer to the links below.
* Tiptap marks: https://tiptap.dev/api/marks
@@ -350,11 +355,46 @@ export class RichTextEditor extends FoundationElement implements ErrorPattern {
Placeholder.configure({
placeholder: '',
showOnlyWhenEditable: false
+ }),
+ customLink.configure({
+ // HTMLAttribute cannot be in camelCase as we want to match it with the name in Tiptap
+ // eslint-disable-next-line @typescript-eslint/naming-convention
+ HTMLAttributes: {
+ rel: 'noopener noreferrer',
+ target: null
+ },
+ openOnClick: false,
+ linkOnPaste: false,
+ validate: href => /^https?:\/\//.test(href)
})
]
});
}
+ /**
+ * Extending the default link mark schema defined in the TipTap.
+ *
+ * "exclude": https://prosemirror.net/docs/ref/#model.MarkSpec.excludes
+ * "inclusive": https://prosemirror.net/docs/ref/#model.MarkSpec.inclusive
+ * "parseHTML": https://tiptap.dev/guide/custom-extensions#parse-html
+ * "renderHTML": https://tiptap.dev/guide/custom-extensions/#render-html
+ */
+ private getCustomLinkExtension(): Mark {
+ return Link.extend({
+ excludes: '_',
+ inclusive: false,
+ parseHTML() {
+ return [{ tag: anchorTag }];
+ },
+ // HTMLAttribute cannot be in camelCase as we want to match it with the name in Tiptap
+ // eslint-disable-next-line @typescript-eslint/naming-convention
+ renderHTML({ HTMLAttributes }) {
+ HTMLAttributes.tabindex = '-1';
+ return [anchorTag, HTMLAttributes];
+ }
+ });
+ }
+
/**
* This function takes the Fragment from parseMarkdownToDOM function and return the serialized string using XMLSerializer
*/
diff --git a/packages/nimble-components/src/rich-text/editor/styles.ts b/packages/nimble-components/src/rich-text/editor/styles.ts
index 2af9ce8127..aac2bba140 100644
--- a/packages/nimble-components/src/rich-text/editor/styles.ts
+++ b/packages/nimble-components/src/rich-text/editor/styles.ts
@@ -77,6 +77,12 @@ export const styles = css`
border: ${borderWidth} solid rgba(${borderRgbPartialColor}, 0.1);
}
+ :host([disabled]) nimble-anchor::part(control) {
+ color: ${bodyDisabledFontColor};
+ fill: currentcolor;
+ cursor: default;
+ }
+
:host([error-visible]) .container {
border-bottom-color: ${failColor};
}
@@ -188,6 +194,23 @@ export const styles = css`
color: ${controlLabelDisabledFontColor};
}
+ nimble-anchor {
+ white-space: normal;
+ ${
+ /**
+ * Restricting the pointer events for the following reasons:
+ * 1. nimble-anchor inside a "contenteditable" div is not working as native anchor HTML anchor tag.
+ * i.e. clicking on the link opens in the same tab whereas the default behavior of native HTML anchor
+ * tag is not clickable inside "contenteditable" div.
+ * 2. Restricting the user from opening a link using the right-click context menu: If the user manually edits
+ * the link, the 'href' attribute of the anchor tag will not be updated. If they attempt to open it using
+ * the right-click context menu with 'Open in new tab/window,' it will still navigate to the link specified
+ * in the 'href' attribute.
+ */ ''
+ }
+ pointer-events: none;
+ }
+
.footer-section {
display: flex;
justify-content: space-between;
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
index aa639921b6..05b440680e 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
@@ -105,15 +105,15 @@ export class RichTextEditorPageObject {
}
public async setEditorTextContent(value: string): Promise {
- let lastElement = this.getTiptapEditor()?.lastElementChild;
-
- while (lastElement?.lastElementChild) {
- lastElement = lastElement?.lastElementChild;
- }
+ const lastElement = this.getEditorLastChildElement();
lastElement!.parentElement!.textContent = value;
await waitForUpdatesAsync();
}
+ public getEditorLastChildAttribute(attribute: string): string {
+ return this.getEditorLastChildElement()?.getAttribute(attribute) || '';
+ }
+
public getEditorFirstChildTagName(): string {
return this.getTiptapEditor()?.firstElementChild?.tagName ?? '';
}
@@ -136,6 +136,28 @@ export class RichTextEditorPageObject {
.map(el => el.textContent || '');
}
+ public getEditorTagNamesWithClosingTags(): string[] {
+ const tagNames: string[] = [];
+ const tiptapEditor = this.getTiptapEditor();
+
+ const processNode = (node: Node): void => {
+ if (node.nodeType === Node.ELEMENT_NODE) {
+ const el = node as Element;
+ tagNames.push(el.tagName);
+
+ el.childNodes.forEach(processNode);
+
+ tagNames.push(`/${el.tagName}`);
+ }
+ };
+
+ if (tiptapEditor) {
+ processNode(tiptapEditor);
+ }
+
+ return tagNames.slice(1, -1);
+ }
+
public getFormattingButtonTextContent(
toolbarButton: ToolbarButton
): string {
@@ -217,4 +239,13 @@ export class RichTextEditorPageObject {
);
return buttons[button];
}
+
+ private getEditorLastChildElement(): Element | null | undefined {
+ let lastElement = this.getTiptapEditor()?.lastElementChild;
+
+ while (lastElement?.lastElementChild) {
+ lastElement = lastElement?.lastElementChild;
+ }
+ return lastElement;
+ }
}
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
index f942be18b2..76fd51bfec 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
@@ -30,7 +30,7 @@ const metadata: Meta = {
}
};
-const richTextMarkdownString = '1. **Bold*Italics***';
+const richTextMarkdownString = '1. \n2. **Bold*Italics***';
export default metadata;
@@ -63,7 +63,7 @@ const component = (
${() => footerHiddenName} ${() => errorStateName} ${() => placeholderName} ${() => disabledName}
<${richTextEditorTag}
- style="margin: 5px 0px; width: 500px;"
+ style="margin: 5px 0px; width: 500px; height: 100px"
?disabled="${() => disabled}"
?footer-hidden="${() => footerHidden}"
?error-visible="${() => isError}"
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 0877f829d8..0746e0f369 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
@@ -625,6 +625,160 @@ describe('RichTextEditor', () => {
'bold, italics and bullet list'
]);
});
+
+ describe('Absolute link interactions in the editor', () => {
+ it('should change the text to "nimble-anchor" tag when it is a valid absolute link', async () => {
+ await pageObject.setEditorTextContent(
+ 'https://nimble.ni.dev/ '
+ );
+
+ expect(pageObject.getEditorTagNames()).toEqual([
+ 'P',
+ 'NIMBLE-ANCHOR'
+ ]);
+ expect(pageObject.getEditorLeafContents()).toEqual([
+ 'https://nimble.ni.dev/'
+ ]);
+ });
+
+ it('should have the right attributes to the nimble-anchor', async () => {
+ await pageObject.setEditorTextContent(
+ 'https://nimble.ni.dev/ '
+ );
+
+ expect(pageObject.getEditorLastChildAttribute('href')).toBe(
+ 'https://nimble.ni.dev/'
+ );
+ expect(pageObject.getEditorLastChildAttribute('rel')).toBe(
+ 'noopener noreferrer'
+ );
+ expect(pageObject.getEditorLastChildAttribute('tabindex')).toBe(
+ '-1'
+ );
+ });
+
+ it('should not affect bold formatting on the link in editor', async () => {
+ await pageObject.clickFooterButton(ToolbarButton.bold);
+ await pageObject.setEditorTextContent(
+ 'https://nimble.ni.dev/ '
+ );
+
+ expect(pageObject.getEditorTagNamesWithClosingTags()).toEqual([
+ 'P',
+ 'NIMBLE-ANCHOR',
+ '/NIMBLE-ANCHOR',
+ 'STRONG',
+ '/STRONG',
+ '/P'
+ ]);
+ expect(pageObject.getEditorLeafContents()).toEqual([
+ 'https://nimble.ni.dev/',
+ ' '
+ ]);
+ });
+
+ it('should not affect italics formatting on the link in editor', async () => {
+ await pageObject.clickFooterButton(ToolbarButton.italics);
+ await pageObject.setEditorTextContent(
+ 'https://nimble.ni.dev/ '
+ );
+
+ expect(pageObject.getEditorTagNamesWithClosingTags()).toEqual([
+ 'P',
+ 'NIMBLE-ANCHOR',
+ '/NIMBLE-ANCHOR',
+ 'EM',
+ '/EM',
+ '/P'
+ ]);
+ expect(pageObject.getEditorLeafContents()).toEqual([
+ 'https://nimble.ni.dev/',
+ ' '
+ ]);
+ });
+
+ it('should able to add links to the bullet list', async () => {
+ await pageObject.setEditorTextContent(
+ 'https://nimble.ni.dev/ '
+ );
+ await pageObject.clickFooterButton(ToolbarButton.bulletList);
+
+ expect(pageObject.getEditorTagNames()).toEqual([
+ 'UL',
+ 'LI',
+ 'P',
+ 'NIMBLE-ANCHOR'
+ ]);
+ expect(pageObject.getEditorLeafContents()).toEqual([
+ 'https://nimble.ni.dev/'
+ ]);
+ });
+
+ it('should able to add links to the numbered list', async () => {
+ await pageObject.setEditorTextContent(
+ 'https://nimble.ni.dev/ '
+ );
+ await pageObject.clickFooterButton(ToolbarButton.numberedList);
+
+ expect(pageObject.getEditorTagNames()).toEqual([
+ 'OL',
+ 'LI',
+ 'P',
+ 'NIMBLE-ANCHOR'
+ ]);
+ expect(pageObject.getEditorLeafContents()).toEqual([
+ 'https://nimble.ni.dev/'
+ ]);
+ });
+
+ describe('various absolute links with different schemas other than https/http should be render as unchanged strings', () => {
+ const notSupportedAbsoluteLink: { name: string }[] = [
+ { name: 'ftp://example.com/files/document.pdf ' },
+ { name: 'mailto:info@example.com ' },
+ { name: 'file:///path/to/local/file.txt ' },
+ { name: 'tel:+1234567890 ' },
+ // eslint-disable-next-line no-script-url
+ { name: 'javascript:void(0) ' },
+ { name: 'data:image/png;base64,iVBORw0KG... ' },
+ { name: 'ftps://example.com/files/document.pdf ' },
+ { name: 'ssh://username@example.com ' },
+ { name: 'urn:isbn:0451450523 ' },
+ {
+ name: 'magnet:?xt=urn:btih:8c6dcd8d4f9151cb5cc01c68225b92db417c411f&dn=ExampleFile.iso '
+ },
+ {
+ name: 'bitcoin:1Hf1KqNPZzkFJ5Wv8VPop9uaF5RjKN3N9s?amount=0.001 '
+ },
+ // eslint-disable-next-line no-script-url
+ { name: 'javascript:vbscript:alert("not alert") ' },
+ { name: 'test://test.com ' }
+ ];
+
+ const focused: string[] = [];
+ const disabled: string[] = [];
+ for (const value of notSupportedAbsoluteLink) {
+ const specType = getSpecTypeByNamedList(
+ value,
+ focused,
+ disabled
+ );
+ specType(
+ `string "${value.name}" renders as plain text "${value.name}" within paragraph tag`,
+ // eslint-disable-next-line @typescript-eslint/no-loop-func
+ async () => {
+ await pageObject.setEditorTextContent(value.name);
+
+ expect(pageObject.getEditorTagNames()).toEqual([
+ 'P'
+ ]);
+ expect(pageObject.getEditorLeafContents()).toEqual([
+ value.name
+ ]);
+ }
+ );
+ }
+ });
+ });
});
describe('various wacky string values input into the editor', () => {
@@ -670,6 +824,193 @@ describe('RichTextEditor', () => {
]);
});
+ describe('Absolute link markdown tests', () => {
+ describe('asserting rendered links in the editor', () => {
+ it('absolute link markdown string to "nimble-anchor" tags with the link as the text content', () => {
+ element.setMarkdown(' ');
+
+ expect(pageObject.getEditorTagNames()).toEqual([
+ 'P',
+ 'NIMBLE-ANCHOR'
+ ]);
+ expect(pageObject.getEditorLeafContents()).toEqual([
+ 'https://nimble.ni.dev/'
+ ]);
+ expect(pageObject.getEditorLastChildAttribute('href')).toBe(
+ 'https://nimble.ni.dev/'
+ );
+ });
+
+ it('bulleted list with absolute links markdown string to "ul", "li" and "nimble-anchor" tags', () => {
+ element.setMarkdown('* ');
+
+ expect(pageObject.getEditorTagNames()).toEqual([
+ 'UL',
+ 'LI',
+ 'P',
+ 'NIMBLE-ANCHOR'
+ ]);
+ expect(pageObject.getEditorLeafContents()).toEqual([
+ 'https://nimble.ni.dev/'
+ ]);
+ expect(pageObject.getEditorLastChildAttribute('href')).toBe(
+ 'https://nimble.ni.dev/'
+ );
+ });
+
+ it('numbered list with absolute links markdown string to "ol", "li" and "nimble-anchor" tags', () => {
+ element.setMarkdown('1. ');
+
+ expect(pageObject.getEditorTagNames()).toEqual([
+ 'OL',
+ 'LI',
+ 'P',
+ 'NIMBLE-ANCHOR'
+ ]);
+ expect(pageObject.getEditorLeafContents()).toEqual([
+ 'https://nimble.ni.dev/'
+ ]);
+ expect(pageObject.getEditorLastChildAttribute('href')).toBe(
+ 'https://nimble.ni.dev/'
+ );
+ });
+
+ it('absolute links in bold markdown string should not be parsed to "strong" tag', () => {
+ element.setMarkdown('** **');
+
+ expect(pageObject.getEditorTagNames()).toEqual([
+ 'P',
+ 'NIMBLE-ANCHOR'
+ ]);
+ expect(pageObject.getEditorLeafContents()).toEqual([
+ 'https://nimble.ni.dev/'
+ ]);
+ expect(pageObject.getEditorLastChildAttribute('href')).toBe(
+ 'https://nimble.ni.dev/'
+ );
+ });
+
+ it('absolute links in italics markdown string should not be parsed to "em" tag', () => {
+ element.setMarkdown('* *');
+
+ expect(pageObject.getEditorTagNames()).toEqual([
+ 'P',
+ 'NIMBLE-ANCHOR'
+ ]);
+ expect(pageObject.getEditorLeafContents()).toEqual([
+ 'https://nimble.ni.dev/'
+ ]);
+ expect(pageObject.getEditorLastChildAttribute('href')).toBe(
+ 'https://nimble.ni.dev/'
+ );
+ });
+
+ it('absolute links in both bold and italics markdown string should not be parsed to "strong" and "em" tag', () => {
+ element.setMarkdown('___ ___');
+
+ expect(pageObject.getEditorTagNames()).toEqual([
+ 'P',
+ 'NIMBLE-ANCHOR'
+ ]);
+ expect(pageObject.getEditorLeafContents()).toEqual([
+ 'https://nimble.ni.dev/'
+ ]);
+ expect(pageObject.getEditorLastChildAttribute('href')).toBe(
+ 'https://nimble.ni.dev/'
+ );
+ });
+
+ it('adding marks like bold inside absolute links should not be parsed to "strong" tag', () => {
+ element.setMarkdown(' ');
+
+ expect(pageObject.getEditorTagNames()).toEqual([
+ 'P',
+ 'NIMBLE-ANCHOR'
+ ]);
+ expect(pageObject.getEditorLeafContents()).toEqual([
+ 'https://**nimble**.ni.dev/'
+ ]);
+ expect(pageObject.getEditorLastChildAttribute('href')).toBe(
+ 'https://**nimble**.ni.dev/'
+ );
+ });
+ });
+
+ describe('asserting getMarkdown for rendered links', () => {
+ it('absolute link markdown string', () => {
+ element.setMarkdown(' ');
+
+ expect(element.getMarkdown()).toEqual(
+ ' '
+ );
+ });
+
+ it('bulleted list with absolute links markdown string', () => {
+ element.setMarkdown('* ');
+
+ expect(element.getMarkdown()).toEqual(
+ '* '
+ );
+ });
+
+ it('numbered list with absolute links markdown string', () => {
+ element.setMarkdown('1. ');
+
+ expect(element.getMarkdown()).toEqual(
+ '1. '
+ );
+ });
+
+ it('absolute links in bold markdown string should not be serialized to link in bold markdown', () => {
+ element.setMarkdown('** **');
+
+ expect(element.getMarkdown()).toEqual(
+ ' '
+ );
+ });
+
+ it('absolute links in italics markdown string should not be serialized to link in italics markdown', () => {
+ element.setMarkdown('* *');
+
+ expect(element.getMarkdown()).toEqual(
+ ' '
+ );
+ });
+
+ it('absolute links in both bold and italics markdown string should not be serialized to link in bold and italics markdown', () => {
+ element.setMarkdown('___ ___');
+
+ expect(element.getMarkdown()).toEqual(
+ ' '
+ );
+ });
+
+ it('adding marks like bold inside absolute links should not be serialized to bold markdown', () => {
+ element.setMarkdown(' ');
+
+ expect(element.getMarkdown()).toEqual(
+ ' '
+ );
+ });
+
+ it('adding marks like italics inside absolute links should not be serialized to italics markdown', () => {
+ element.setMarkdown(' ');
+
+ expect(element.getMarkdown()).toEqual(
+ ' '
+ );
+ });
+
+ it('adding both the italics and bold inside absolute links should not be serialized to bold and italics markdown', () => {
+ element.setMarkdown(' ');
+
+ expect(element.getMarkdown()).toEqual(
+ ' '
+ );
+ });
+ });
+ });
+
it('Should return a empty string when empty string is assigned', () => {
element.setMarkdown('markdown string');
expect(element.getMarkdown()).toBe('markdown string');
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
index a1a34763fa..9d9a42f3f7 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
@@ -12,6 +12,7 @@ import {
type LabelUserArgs
} from '../../../label-provider/base/tests/label-user-stories-utils';
import { labelProviderRichTextTag } from '../../../label-provider/rich-text';
+import { richTextMarkdownString } from '../../../utilities/tests/rich-text-markdown-string';
// eslint-disable-next-line @typescript-eslint/no-empty-interface
interface RichTextEditorArgs extends LabelUserArgs {
@@ -38,20 +39,9 @@ const exampleDataType = {
const plainString = 'Plain text' as const;
-const markdownString = `
-Supported rich text formatting options:
-1. **Bold**
-2. *Italics*
-3. Numbered lists
- 1. Option 1
- 2. Option 2
-4. Bulleted lists
- * Option 1
- * Option 2` as const;
-
const dataSets = {
[exampleDataType.plainString]: plainString,
- [exampleDataType.markdownString]: markdownString
+ [exampleDataType.markdownString]: richTextMarkdownString
} as const;
const richTextEditorDescription = 'The rich text editor component allows users to add/edit text formatted with various styling options including bold, italics, numbered lists, and bulleted lists. The editor generates markdown output and takes markdown as input. The markdown flavor used is [CommonMark](https://spec.commonmark.org/0.30/).\n\n See the [rich text viewer](?path=/docs/incubating-rich-text-viewer--docs) component to render markdown without allowing editing.';
@@ -86,6 +76,7 @@ const metadata: Meta = {
})}
<${richTextEditorTag}
${ref('editorRef')}
+ style="height: 160px"
data-unused="${x => x.setMarkdownData(x)}"
?disabled="${x => x.disabled}"
?footer-hidden="${x => x.footerHidden}"
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 e37f4045e0..bd36eeaa64 100644
--- a/packages/nimble-components/src/rich-text/models/markdown-parser.ts
+++ b/packages/nimble-components/src/rich-text/models/markdown-parser.ts
@@ -3,14 +3,18 @@ import {
defaultMarkdownParser,
MarkdownParser
} from 'prosemirror-markdown';
-import { DOMSerializer } from 'prosemirror-model';
+import { DOMSerializer, Schema } from 'prosemirror-model';
+import { anchorTag } from '../../anchor';
/**
* Provides markdown parser for rich text components
*/
export class RichTextMarkdownParser {
+ private static readonly updatedSchema = this.getUpdatedSchema();
private static readonly markdownParser = this.initializeMarkdownParser();
- private static readonly domSerializer = DOMSerializer.fromSchema(schema);
+ private static readonly domSerializer = DOMSerializer.fromSchema(
+ this.updatedSchema
+ );
/**
* This function takes a markdown string, parses it using the ProseMirror MarkdownParser, serializes the parsed content into a
@@ -46,10 +50,39 @@ export class RichTextMarkdownParser {
'autolink'
]);
+ supportedTokenizerRules.validateLink = href => /^https?:\/\//.test(href);
+
return new MarkdownParser(
- schema,
+ this.updatedSchema,
supportedTokenizerRules,
defaultMarkdownParser.tokens
);
}
+
+ private static getUpdatedSchema(): Schema {
+ return new Schema({
+ nodes: schema.spec.nodes,
+ marks: {
+ link: {
+ attrs: {
+ href: {},
+ rel: { default: 'noopener noreferrer' }
+ },
+ inclusive: false,
+ excludes: '_',
+ toDOM(node) {
+ return [
+ anchorTag,
+ {
+ href: node.attrs.href as Attr,
+ rel: node.attrs.rel as Attr
+ }
+ ];
+ }
+ },
+ em: schema.spec.marks.get('em')!,
+ strong: schema.spec.marks.get('strong')!
+ }
+ });
+ }
}
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 cad1e14484..61be5da5dd 100644
--- a/packages/nimble-components/src/rich-text/models/markdown-serializer.ts
+++ b/packages/nimble-components/src/rich-text/models/markdown-serializer.ts
@@ -51,7 +51,15 @@ export class RichTextMarkdownSerializer {
};
const marks = {
italic: defaultMarkdownSerializer.marks.em!,
- bold: defaultMarkdownSerializer.marks.strong!
+ bold: defaultMarkdownSerializer.marks.strong!,
+ // Autolink markdown in CommonMark flavor: https://spec.commonmark.org/0.30/#autolinks
+ link: {
+ open: '<',
+ close: '>',
+ mixable: true,
+ escape: false,
+ expelEnclosingWhitespace: true
+ }
};
return new MarkdownSerializer(nodes, marks);
}
diff --git a/packages/nimble-components/src/rich-text/models/tests/markdown-parser.spec.ts b/packages/nimble-components/src/rich-text/models/tests/markdown-parser.spec.ts
index fa3f274150..bb25e3d366 100644
--- a/packages/nimble-components/src/rich-text/models/tests/markdown-parser.spec.ts
+++ b/packages/nimble-components/src/rich-text/models/tests/markdown-parser.spec.ts
@@ -21,6 +21,24 @@ describe('Markdown parser', () => {
return nodes;
}
+ function getEditorLastChildAttribute(
+ attribute: string,
+ doc: DocumentFragment
+ ): string {
+ return getEditorLastChildElement(doc)?.getAttribute(attribute) || '';
+ }
+
+ function getEditorLastChildElement(
+ doc: DocumentFragment
+ ): Element | null | undefined {
+ let lastElement = doc.lastElementChild;
+
+ while (lastElement?.lastElementChild) {
+ lastElement = lastElement?.lastElementChild;
+ }
+ return lastElement;
+ }
+
describe('supported rich text formatting options from markdown string to its respective HTML elements', () => {
it('bold markdown string("**") to "strong" HTML tag', () => {
const doc = RichTextMarkdownParser.parseMarkdownToDOM('**Bold**');
@@ -230,9 +248,185 @@ describe('Markdown parser', () => {
).toEqual(['Bulleted list in italics']);
});
+ describe('Absolute link', () => {
+ it('absolute link markdown string to "a" tags with the link as the text content', () => {
+ const doc = RichTextMarkdownParser.parseMarkdownToDOM(
+ ' '
+ );
+
+ expect(
+ getTagsFromDocumentFragment(doc as DocumentFragment)
+ ).toEqual(['P', 'NIMBLE-ANCHOR']);
+ expect(
+ getLeafContentsFromDocumentFragment(doc as DocumentFragment)
+ ).toEqual(['https://nimble.ni.dev/']);
+ expect(
+ getEditorLastChildAttribute('href', doc as DocumentFragment)
+ ).toBe('https://nimble.ni.dev/');
+ });
+
+ it('absolute link should add "rel" attribute', () => {
+ const doc = RichTextMarkdownParser.parseMarkdownToDOM(
+ ' '
+ );
+
+ expect(
+ getEditorLastChildAttribute('rel', doc as DocumentFragment)
+ ).toBe('noopener noreferrer');
+ });
+
+ it('bulleted list with absolute links markdown string to "ul", "li" and "a" HTML tags', () => {
+ const doc = RichTextMarkdownParser.parseMarkdownToDOM(
+ '* '
+ );
+
+ expect(
+ getTagsFromDocumentFragment(doc as DocumentFragment)
+ ).toEqual(['UL', 'LI', 'P', 'NIMBLE-ANCHOR']);
+ expect(
+ getLeafContentsFromDocumentFragment(doc as DocumentFragment)
+ ).toEqual(['https://nimble.ni.dev/']);
+ expect(
+ getEditorLastChildAttribute('href', doc as DocumentFragment)
+ ).toBe('https://nimble.ni.dev/');
+ });
+
+ it('numbered list with absolute links markdown string to "ol", "li" and "a" HTML tags', () => {
+ const doc = RichTextMarkdownParser.parseMarkdownToDOM(
+ '1. '
+ );
+
+ expect(
+ getTagsFromDocumentFragment(doc as DocumentFragment)
+ ).toEqual(['OL', 'LI', 'P', 'NIMBLE-ANCHOR']);
+ expect(
+ getLeafContentsFromDocumentFragment(doc as DocumentFragment)
+ ).toEqual(['https://nimble.ni.dev/']);
+ expect(
+ getEditorLastChildAttribute('href', doc as DocumentFragment)
+ ).toBe('https://nimble.ni.dev/');
+ });
+
+ it('absolute links in bold markdown string should not be parsed to "strong" HTML tag', () => {
+ const doc = RichTextMarkdownParser.parseMarkdownToDOM(
+ '** **'
+ );
+
+ expect(
+ getTagsFromDocumentFragment(doc as DocumentFragment)
+ ).toEqual(['P', 'NIMBLE-ANCHOR']);
+ expect(
+ getLeafContentsFromDocumentFragment(doc as DocumentFragment)
+ ).toEqual(['https://nimble.ni.dev/']);
+ expect(
+ getEditorLastChildAttribute('href', doc as DocumentFragment)
+ ).toBe('https://nimble.ni.dev/');
+ });
+
+ it('absolute links in italics markdown string should not be parsed to "em" HTML tag', () => {
+ const doc = RichTextMarkdownParser.parseMarkdownToDOM(
+ '* *'
+ );
+
+ expect(
+ getTagsFromDocumentFragment(doc as DocumentFragment)
+ ).toEqual(['P', 'NIMBLE-ANCHOR']);
+ expect(
+ getLeafContentsFromDocumentFragment(doc as DocumentFragment)
+ ).toEqual(['https://nimble.ni.dev/']);
+ expect(
+ getEditorLastChildAttribute('href', doc as DocumentFragment)
+ ).toBe('https://nimble.ni.dev/');
+ });
+
+ it('absolute links in both bold and italics markdown string should not be parsed to "strong" and "em" HTML tag', () => {
+ const doc = RichTextMarkdownParser.parseMarkdownToDOM(
+ '___ ___'
+ );
+
+ expect(
+ getTagsFromDocumentFragment(doc as DocumentFragment)
+ ).toEqual(['P', 'NIMBLE-ANCHOR']);
+ expect(
+ getLeafContentsFromDocumentFragment(doc as DocumentFragment)
+ ).toEqual(['https://nimble.ni.dev/']);
+ expect(
+ getEditorLastChildAttribute('href', doc as DocumentFragment)
+ ).toBe('https://nimble.ni.dev/');
+ });
+
+ it('adding marks like bold inside absolute links should not be parsed to "strong" HTML tag', () => {
+ const doc = RichTextMarkdownParser.parseMarkdownToDOM(
+ ' '
+ );
+
+ expect(
+ getTagsFromDocumentFragment(doc as DocumentFragment)
+ ).toEqual(['P', 'NIMBLE-ANCHOR']);
+ expect(
+ getLeafContentsFromDocumentFragment(doc as DocumentFragment)
+ ).toEqual(['https://**nimble**.ni.dev/']);
+ expect(
+ getEditorLastChildAttribute('href', doc as DocumentFragment)
+ ).toBe('https://**nimble**.ni.dev/');
+ });
+
+ describe('various absolute links with different schemas other than https/http should be render as unchanged strings', () => {
+ const notSupportedAbsoluteLink: { name: string }[] = [
+ { name: '' },
+ { name: '' },
+ { name: '' },
+ { name: '' },
+ { name: '' },
+ { name: '' },
+ { name: '' },
+ { name: '' },
+ { name: '' },
+ {
+ name: ''
+ },
+ {
+ name: ''
+ },
+ { name: '' },
+ { name: '' }
+ ];
+
+ const focused: string[] = [];
+ const disabled: string[] = [];
+ for (const value of notSupportedAbsoluteLink) {
+ const specType = getSpecTypeByNamedList(
+ value,
+ focused,
+ disabled
+ );
+ specType(
+ `string "${value.name}" renders as plain text "${value.name}" within paragraph tag`,
+ // eslint-disable-next-line @typescript-eslint/no-loop-func
+ () => {
+ const doc = RichTextMarkdownParser.parseMarkdownToDOM(
+ value.name
+ );
+
+ expect(
+ getTagsFromDocumentFragment(
+ doc as DocumentFragment
+ )
+ ).toEqual(['P']);
+ expect(
+ getLeafContentsFromDocumentFragment(
+ doc as DocumentFragment
+ )
+ ).toEqual([value.name]);
+ }
+ );
+ }
+ });
+ });
+
it('combination of all supported markdown string', () => {
const doc = RichTextMarkdownParser.parseMarkdownToDOM(
- '1. ***Numbered list with bold and italics***\n* ___Bulleted list with bold and italics___'
+ '1. ***Numbered list with bold and italics***\n* ___Bulleted list with bold and italics___\n* '
);
expect(
@@ -247,13 +441,17 @@ describe('Markdown parser', () => {
'LI',
'P',
'EM',
- 'STRONG'
+ 'STRONG',
+ 'LI',
+ 'P',
+ 'NIMBLE-ANCHOR'
]);
expect(
getLeafContentsFromDocumentFragment(doc as DocumentFragment)
).toEqual([
'Numbered list with bold and italics',
- 'Bulleted list with bold and italics'
+ 'Bulleted list with bold and italics',
+ 'https://nimble.ni.dev/'
]);
});
});
diff --git a/packages/nimble-components/src/rich-text/models/tests/markdown-serializer.spec.ts b/packages/nimble-components/src/rich-text/models/tests/markdown-serializer.spec.ts
index 161460836d..05502d5f97 100644
--- a/packages/nimble-components/src/rich-text/models/tests/markdown-serializer.spec.ts
+++ b/packages/nimble-components/src/rich-text/models/tests/markdown-serializer.spec.ts
@@ -7,6 +7,7 @@ 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 Link from '@tiptap/extension-link';
import type { Node } from 'prosemirror-model';
import { RichTextMarkdownSerializer } from '../markdown-serializer';
import { getSpecTypeByNamedList } from '../../../utilities/tests/parameterized';
@@ -22,7 +23,10 @@ describe('Markdown serializer', () => {
OrderedList,
ListItem,
Bold,
- Italic
+ Italic,
+ Link.extend({
+ excludes: '_'
+ })
]
});
@@ -51,11 +55,31 @@ describe('Markdown serializer', () => {
html: 'Italics ',
markdown: '*Italics*'
},
+ {
+ name: 'Link',
+ html: 'Link
',
+ markdown: ' '
+ },
{
name: 'Bold and Italics',
html: 'Bold and Italics ',
markdown: '***Bold and Italics***'
},
+ {
+ name: 'Link and Bold',
+ html: 'Link and Bold
',
+ markdown: ' '
+ },
+ {
+ name: 'Link and Italics',
+ html: 'Link and Italics
',
+ markdown: ' '
+ },
+ {
+ name: 'Link, Bold and Italics',
+ html: 'Link, Bold and Italics
',
+ markdown: ' '
+ },
{
name: 'Numbered list',
html: 'Numbered list
',
@@ -76,6 +100,11 @@ describe('Markdown serializer', () => {
html: 'Numbered list with italics
',
markdown: '1. *Numbered list with italics*'
},
+ {
+ name: 'Numbered list with link',
+ html: 'Numbered list with link
',
+ markdown: '1. '
+ },
{
name: 'Bullet list',
html: '',
@@ -95,6 +124,11 @@ describe('Markdown serializer', () => {
name: 'Bullet list with italics',
html: '',
markdown: '* *Bullet list with italics*'
+ },
+ {
+ name: 'Bullet list with link',
+ html: '',
+ markdown: '* '
}
];
@@ -149,7 +183,6 @@ describe('Markdown serializer', () => {
html: 'Highlight ',
plainText: 'Highlight'
},
- { name: 'Link', html: 'Link ', plainText: 'Link' },
{
name: 'Strikethrough',
html: 'Strikethrough ',
diff --git a/packages/nimble-components/src/rich-text/viewer/styles.ts b/packages/nimble-components/src/rich-text/viewer/styles.ts
index 0d9d8fcbfa..5d41ae0709 100644
--- a/packages/nimble-components/src/rich-text/viewer/styles.ts
+++ b/packages/nimble-components/src/rich-text/viewer/styles.ts
@@ -1,11 +1,6 @@
import { css } from '@microsoft/fast-element';
import { display } from '@microsoft/fast-foundation';
-import {
- bodyFont,
- bodyFontColor,
- linkActiveFontColor,
- linkFontColor
-} from '../../theme-provider/design-tokens';
+import { bodyFont, bodyFontColor } from '../../theme-provider/design-tokens';
export const styles = css`
${display('flex')}
@@ -45,13 +40,4 @@ export const styles = css`
li > p:empty {
display: none;
}
-
- a {
- word-break: break-all;
- color: ${linkFontColor};
- }
-
- a:active {
- color: ${linkActiveFontColor};
- }
`;
From fd65fdb7b81b6cf876f15662e0f10dc00ab6a396 Mon Sep 17 00:00:00 2001
From: Vivin Krishna <123377523+vivinkrishna-ni@users.noreply.github.com>
Date: Thu, 7 Sep 2023 20:37:16 +0530
Subject: [PATCH 33/56] Minor comment description
---
packages/nimble-components/src/rich-text/editor/styles.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/nimble-components/src/rich-text/editor/styles.ts b/packages/nimble-components/src/rich-text/editor/styles.ts
index aac2bba140..1166f9adea 100644
--- a/packages/nimble-components/src/rich-text/editor/styles.ts
+++ b/packages/nimble-components/src/rich-text/editor/styles.ts
@@ -199,7 +199,7 @@ export const styles = css`
${
/**
* Restricting the pointer events for the following reasons:
- * 1. nimble-anchor inside a "contenteditable" div is not working as native anchor HTML anchor tag.
+ * 1. nimble-anchor inside a "contenteditable" div is not working as native HTML anchor tag.
* i.e. clicking on the link opens in the same tab whereas the default behavior of native HTML anchor
* tag is not clickable inside "contenteditable" div.
* 2. Restricting the user from opening a link using the right-click context menu: If the user manually edits
From e1ed701572decf1688c1c3bb9161221a2e44837a Mon Sep 17 00:00:00 2001
From: Vivin Krishna <123377523+vivinkrishna-ni@users.noreply.github.com>
Date: Fri, 8 Sep 2023 13:15:04 +0530
Subject: [PATCH 34/56] Updated the comment description about link behavior
---
packages/nimble-components/src/rich-text/editor/styles.ts | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/packages/nimble-components/src/rich-text/editor/styles.ts b/packages/nimble-components/src/rich-text/editor/styles.ts
index 1166f9adea..b12ca76b5e 100644
--- a/packages/nimble-components/src/rich-text/editor/styles.ts
+++ b/packages/nimble-components/src/rich-text/editor/styles.ts
@@ -202,10 +202,11 @@ export const styles = css`
* 1. nimble-anchor inside a "contenteditable" div is not working as native HTML anchor tag.
* i.e. clicking on the link opens in the same tab whereas the default behavior of native HTML anchor
* tag is not clickable inside "contenteditable" div.
+ * Issue link: https://github.com/ni/nimble/issues/1502
* 2. Restricting the user from opening a link using the right-click context menu: If the user manually edits
- * the link, the 'href' attribute of the anchor tag will not be updated. If they attempt to open it using
+ * the link's text content, the 'href' attribute of the anchor tag will not be updated. If they attempt to open it using
* the right-click context menu with 'Open in new tab/window,' it will still navigate to the link specified
- * in the 'href' attribute.
+ * in the 'href' attribute, which may create unnecessary confusion while trying to open the link
*/ ''
}
pointer-events: none;
From b3ba37da4046cb99ddd9b81656765ee6e2af7078 Mon Sep 17 00:00:00 2001
From: Sai krishnan Perumal <123591928+saikrishnan-ni@users.noreply.github.com>
Date: Fri, 8 Sep 2023 13:43:54 +0530
Subject: [PATCH 35/56] refactor: add parser utils for reusable functions
Signed-off-by: Sai krishnan Perumal <123591928+saikrishnan-ni@users.noreply.github.com>
---
.../testing/rich-text-editor.pageobject.ts | 11 +++--------
.../models/testing/markdown-parser-utils.ts | 17 +++++++++++++++++
.../models/tests/markdown-parser.spec.ts | 19 +------------------
3 files changed, 21 insertions(+), 26 deletions(-)
create mode 100644 packages/nimble-components/src/rich-text/models/testing/markdown-parser-utils.ts
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
index aa639921b6..d0e0fb42ce 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
@@ -3,6 +3,7 @@ import type { RichTextEditor } from '..';
import { waitForUpdatesAsync } from '../../../testing/async-helpers';
import type { ToggleButton } from '../../../toggle-button';
import type { ToolbarButton } from './types';
+import { getTagsFromDocumentFragment, getLeafContentsFromDocumentFragment } from '../../models/testing/markdown-parser-utils';
/**
* Page object for the `nimble-rich-text-editor` component.
@@ -123,17 +124,11 @@ export class RichTextEditorPageObject {
}
public getEditorTagNames(): string[] {
- return Array.from(this.getTiptapEditor()!.querySelectorAll('*')).map(
- el => el.tagName
- );
+ return getTagsFromDocumentFragment(this.getTiptapEditor() as HTMLElement);
}
public getEditorLeafContents(): string[] {
- return Array.from(this.getTiptapEditor()!.querySelectorAll('*'))
- .filter((el, _) => {
- return el.children.length === 0;
- })
- .map(el => el.textContent || '');
+ return getLeafContentsFromDocumentFragment(this.getTiptapEditor() as HTMLElement);
}
public getFormattingButtonTextContent(
diff --git a/packages/nimble-components/src/rich-text/models/testing/markdown-parser-utils.ts b/packages/nimble-components/src/rich-text/models/testing/markdown-parser-utils.ts
new file mode 100644
index 0000000000..65de11d2f3
--- /dev/null
+++ b/packages/nimble-components/src/rich-text/models/testing/markdown-parser-utils.ts
@@ -0,0 +1,17 @@
+export const getTagsFromDocumentFragment = (doc: DocumentFragment | HTMLElement): string[] => {
+ const nodes = Array.from(doc.querySelectorAll('*')).map(
+ el => el.tagName
+ );
+ return nodes;
+};
+
+export const getLeafContentsFromDocumentFragment = (
+ doc: DocumentFragment | HTMLElement
+): string[] => {
+ const nodes = Array.from(doc.querySelectorAll('*'))
+ .filter((el, _) => {
+ return el.children.length === 0;
+ })
+ .map(el => el.textContent || '');
+ return nodes;
+};
\ No newline at end of file
diff --git a/packages/nimble-components/src/rich-text/models/tests/markdown-parser.spec.ts b/packages/nimble-components/src/rich-text/models/tests/markdown-parser.spec.ts
index 72613e6b45..436fef918c 100644
--- a/packages/nimble-components/src/rich-text/models/tests/markdown-parser.spec.ts
+++ b/packages/nimble-components/src/rich-text/models/tests/markdown-parser.spec.ts
@@ -1,26 +1,9 @@
import { getSpecTypeByNamedList } from '../../../utilities/tests/parameterized';
import { wackyStrings } from '../../../utilities/tests/wacky-strings';
import { RichTextMarkdownParser } from '../markdown-parser';
+import { getLeafContentsFromDocumentFragment, getTagsFromDocumentFragment } from '../testing/markdown-parser-utils';
describe('Markdown parser', () => {
- function getTagsFromDocumentFragment(doc: DocumentFragment): string[] {
- const nodes = Array.from(doc.querySelectorAll('*')).map(
- el => el.tagName
- );
- return nodes;
- }
-
- function getLeafContentsFromDocumentFragment(
- doc: DocumentFragment
- ): string[] {
- const nodes = Array.from(doc.querySelectorAll('*'))
- .filter((el, _) => {
- return el.children.length === 0;
- })
- .map(el => el.textContent || '');
- return nodes;
- }
-
describe('supported rich text formatting options from markdown string to its respective HTML elements', () => {
it('bold markdown string("**") to "strong" HTML tag', () => {
const doc = RichTextMarkdownParser.parseMarkdownToDOM('**Bold**');
From f6d27d8523d515a512eb1f05d62fb98d17bdda02 Mon Sep 17 00:00:00 2001
From: Sai krishnan Perumal <123591928+saikrishnan-ni@users.noreply.github.com>
Date: Fri, 8 Sep 2023 13:52:49 +0530
Subject: [PATCH 36/56] refactor:remove unwanted type conversion and update
function naming
Signed-off-by: Sai krishnan Perumal <123591928+saikrishnan-ni@users.noreply.github.com>
---
.../testing/rich-text-editor.pageobject.ts | 11 +-
.../models/testing/markdown-parser-utils.ts | 12 +-
.../models/tests/markdown-parser.spec.ts | 247 ++++++++----------
3 files changed, 128 insertions(+), 142 deletions(-)
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
index d0e0fb42ce..fe979d864d 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
@@ -3,7 +3,10 @@ import type { RichTextEditor } from '..';
import { waitForUpdatesAsync } from '../../../testing/async-helpers';
import type { ToggleButton } from '../../../toggle-button';
import type { ToolbarButton } from './types';
-import { getTagsFromDocumentFragment, getLeafContentsFromDocumentFragment } from '../../models/testing/markdown-parser-utils';
+import {
+ getTagsFromElement,
+ getLeafContentsFromElement
+} from '../../models/testing/markdown-parser-utils';
/**
* Page object for the `nimble-rich-text-editor` component.
@@ -124,11 +127,13 @@ export class RichTextEditorPageObject {
}
public getEditorTagNames(): string[] {
- return getTagsFromDocumentFragment(this.getTiptapEditor() as HTMLElement);
+ return getTagsFromElement(this.getTiptapEditor() as HTMLElement);
}
public getEditorLeafContents(): string[] {
- return getLeafContentsFromDocumentFragment(this.getTiptapEditor() as HTMLElement);
+ return getLeafContentsFromElement(
+ this.getTiptapEditor() as HTMLElement
+ );
}
public getFormattingButtonTextContent(
diff --git a/packages/nimble-components/src/rich-text/models/testing/markdown-parser-utils.ts b/packages/nimble-components/src/rich-text/models/testing/markdown-parser-utils.ts
index 65de11d2f3..84a635973b 100644
--- a/packages/nimble-components/src/rich-text/models/testing/markdown-parser-utils.ts
+++ b/packages/nimble-components/src/rich-text/models/testing/markdown-parser-utils.ts
@@ -1,11 +1,11 @@
-export const getTagsFromDocumentFragment = (doc: DocumentFragment | HTMLElement): string[] => {
- const nodes = Array.from(doc.querySelectorAll('*')).map(
- el => el.tagName
- );
+export const getTagsFromElement = (
+ doc: DocumentFragment | HTMLElement
+): string[] => {
+ const nodes = Array.from(doc.querySelectorAll('*')).map(el => el.tagName);
return nodes;
};
-export const getLeafContentsFromDocumentFragment = (
+export const getLeafContentsFromElement = (
doc: DocumentFragment | HTMLElement
): string[] => {
const nodes = Array.from(doc.querySelectorAll('*'))
@@ -14,4 +14,4 @@ export const getLeafContentsFromDocumentFragment = (
})
.map(el => el.textContent || '');
return nodes;
-};
\ No newline at end of file
+};
diff --git a/packages/nimble-components/src/rich-text/models/tests/markdown-parser.spec.ts b/packages/nimble-components/src/rich-text/models/tests/markdown-parser.spec.ts
index 436fef918c..062920ed3f 100644
--- a/packages/nimble-components/src/rich-text/models/tests/markdown-parser.spec.ts
+++ b/packages/nimble-components/src/rich-text/models/tests/markdown-parser.spec.ts
@@ -1,73 +1,53 @@
import { getSpecTypeByNamedList } from '../../../utilities/tests/parameterized';
import { wackyStrings } from '../../../utilities/tests/wacky-strings';
import { RichTextMarkdownParser } from '../markdown-parser';
-import { getLeafContentsFromDocumentFragment, getTagsFromDocumentFragment } from '../testing/markdown-parser-utils';
+import {
+ getLeafContentsFromElement,
+ getTagsFromElement
+} from '../testing/markdown-parser-utils';
describe('Markdown parser', () => {
describe('supported rich text formatting options from markdown string to its respective HTML elements', () => {
it('bold markdown string("**") to "strong" HTML tag', () => {
const doc = RichTextMarkdownParser.parseMarkdownToDOM('**Bold**');
- expect(
- getTagsFromDocumentFragment(doc as DocumentFragment)
- ).toEqual(['P', 'STRONG']);
- expect(
- getLeafContentsFromDocumentFragment(doc as DocumentFragment)
- ).toEqual(['Bold']);
+
+ expect(getTagsFromElement(doc)).toEqual(['P', 'STRONG']);
+ expect(getLeafContentsFromElement(doc)).toEqual(['Bold']);
});
it('bold markdown string("__") to "strong" HTML tag', () => {
const doc = RichTextMarkdownParser.parseMarkdownToDOM('__Bold__');
- expect(
- getTagsFromDocumentFragment(doc as DocumentFragment)
- ).toEqual(['P', 'STRONG']);
- expect(
- getLeafContentsFromDocumentFragment(doc as DocumentFragment)
- ).toEqual(['Bold']);
+ expect(getTagsFromElement(doc)).toEqual(['P', 'STRONG']);
+ expect(getLeafContentsFromElement(doc)).toEqual(['Bold']);
});
it('italics markdown string("*") to "em" HTML tag', () => {
const doc = RichTextMarkdownParser.parseMarkdownToDOM('*Italics*');
- expect(
- getTagsFromDocumentFragment(doc as DocumentFragment)
- ).toEqual(['P', 'EM']);
- expect(
- getLeafContentsFromDocumentFragment(doc as DocumentFragment)
- ).toEqual(['Italics']);
+ expect(getTagsFromElement(doc)).toEqual(['P', 'EM']);
+ expect(getLeafContentsFromElement(doc)).toEqual(['Italics']);
});
it('italics markdown string("_") to "em" HTML tag', () => {
const doc = RichTextMarkdownParser.parseMarkdownToDOM('_Italics_');
- expect(
- getTagsFromDocumentFragment(doc as DocumentFragment)
- ).toEqual(['P', 'EM']);
- expect(
- getLeafContentsFromDocumentFragment(doc as DocumentFragment)
- ).toEqual(['Italics']);
+ expect(getTagsFromElement(doc)).toEqual(['P', 'EM']);
+ expect(getLeafContentsFromElement(doc)).toEqual(['Italics']);
});
it('numbered list markdown string("1.") to "ol" and "li" HTML tags', () => {
const doc = RichTextMarkdownParser.parseMarkdownToDOM('1. Numbered list');
- expect(
- getTagsFromDocumentFragment(doc as DocumentFragment)
- ).toEqual(['OL', 'LI', 'P']);
- expect(
- getLeafContentsFromDocumentFragment(doc as DocumentFragment)
- ).toEqual(['Numbered list']);
+ expect(getTagsFromElement(doc)).toEqual(['OL', 'LI', 'P']);
+ expect(getLeafContentsFromElement(doc)).toEqual(['Numbered list']);
});
it('numbered list markdown string("1)") to "ol" and "li" HTML tags', () => {
const doc = RichTextMarkdownParser.parseMarkdownToDOM('1) Numbered list');
- expect(
- getTagsFromDocumentFragment(doc as DocumentFragment)
- ).toEqual(['OL', 'LI', 'P']);
- expect(
- getLeafContentsFromDocumentFragment(doc as DocumentFragment)
- ).toEqual(['Numbered list']);
+ expect(getTagsFromElement(doc)).toEqual(['OL', 'LI', 'P']);
+ expect(getLeafContentsFromElement(doc)).toEqual(['Numbered list']);
});
it('multiple numbered lists markdown string("1.\n2.") to "ol" and "li" HTML tags', () => {
@@ -75,23 +55,30 @@ describe('Markdown parser', () => {
'1. Option 1\n 2. Option 2'
);
- expect(
- getTagsFromDocumentFragment(doc as DocumentFragment)
- ).toEqual(['OL', 'LI', 'P', 'LI', 'P']);
- expect(
- getLeafContentsFromDocumentFragment(doc as DocumentFragment)
- ).toEqual(['Option 1', 'Option 2']);
+ expect(getTagsFromElement(doc)).toEqual([
+ 'OL',
+ 'LI',
+ 'P',
+ 'LI',
+ 'P'
+ ]);
+ expect(getLeafContentsFromElement(doc)).toEqual([
+ 'Option 1',
+ 'Option 2'
+ ]);
});
it('multiple empty numbered lists markdown string("1.\n2.") to "ol" and "li" HTML tags', () => {
const doc = RichTextMarkdownParser.parseMarkdownToDOM('1. \n 2. ');
- expect(
- getTagsFromDocumentFragment(doc as DocumentFragment)
- ).toEqual(['OL', 'LI', 'P', 'LI', 'P']);
- expect(
- getLeafContentsFromDocumentFragment(doc as DocumentFragment)
- ).toEqual(['', '']);
+ expect(getTagsFromElement(doc)).toEqual([
+ 'OL',
+ 'LI',
+ 'P',
+ 'LI',
+ 'P'
+ ]);
+ expect(getLeafContentsFromElement(doc)).toEqual(['', '']);
});
it('numbered lists that start with numbers and are not sequential to "ol" and "li" HTML tags', () => {
@@ -99,12 +86,17 @@ describe('Markdown parser', () => {
'1. Option 1\n 1. Option 2'
);
- expect(
- getTagsFromDocumentFragment(doc as DocumentFragment)
- ).toEqual(['OL', 'LI', 'P', 'LI', 'P']);
- expect(
- getLeafContentsFromDocumentFragment(doc as DocumentFragment)
- ).toEqual(['Option 1', 'Option 2']);
+ expect(getTagsFromElement(doc)).toEqual([
+ 'OL',
+ 'LI',
+ 'P',
+ 'LI',
+ 'P'
+ ]);
+ expect(getLeafContentsFromElement(doc)).toEqual([
+ 'Option 1',
+ 'Option 2'
+ ]);
});
it('numbered lists if there is some content between lists', () => {
@@ -112,12 +104,16 @@ describe('Markdown parser', () => {
'1. Option 1\n\nSome content in between lists\n\n 2. Option 2'
);
- expect(
- getTagsFromDocumentFragment(doc as DocumentFragment)
- ).toEqual(['OL', 'LI', 'P', 'P', 'OL', 'LI', 'P']);
- expect(
- getLeafContentsFromDocumentFragment(doc as DocumentFragment)
- ).toEqual([
+ expect(getTagsFromElement(doc)).toEqual([
+ 'OL',
+ 'LI',
+ 'P',
+ 'P',
+ 'OL',
+ 'LI',
+ 'P'
+ ]);
+ expect(getLeafContentsFromElement(doc)).toEqual([
'Option 1',
'Some content in between lists',
'Option 2'
@@ -127,34 +123,22 @@ describe('Markdown parser', () => {
it('bulleted list markdown string("*") to "ul" and "li" HTML tags', () => {
const doc = RichTextMarkdownParser.parseMarkdownToDOM('* Bulleted list');
- expect(
- getTagsFromDocumentFragment(doc as DocumentFragment)
- ).toEqual(['UL', 'LI', 'P']);
- expect(
- getLeafContentsFromDocumentFragment(doc as DocumentFragment)
- ).toEqual(['Bulleted list']);
+ expect(getTagsFromElement(doc)).toEqual(['UL', 'LI', 'P']);
+ expect(getLeafContentsFromElement(doc)).toEqual(['Bulleted list']);
});
it('bulleted list markdown string("-") to "ul" and "li" HTML tags', () => {
const doc = RichTextMarkdownParser.parseMarkdownToDOM('- Bulleted list');
- expect(
- getTagsFromDocumentFragment(doc as DocumentFragment)
- ).toEqual(['UL', 'LI', 'P']);
- expect(
- getLeafContentsFromDocumentFragment(doc as DocumentFragment)
- ).toEqual(['Bulleted list']);
+ expect(getTagsFromElement(doc)).toEqual(['UL', 'LI', 'P']);
+ expect(getLeafContentsFromElement(doc)).toEqual(['Bulleted list']);
});
it('bulleted list markdown string("+") to "ul" and "li" HTML tags', () => {
const doc = RichTextMarkdownParser.parseMarkdownToDOM('+ Bulleted list');
- expect(
- getTagsFromDocumentFragment(doc as DocumentFragment)
- ).toEqual(['UL', 'LI', 'P']);
- expect(
- getLeafContentsFromDocumentFragment(doc as DocumentFragment)
- ).toEqual(['Bulleted list']);
+ expect(getTagsFromElement(doc)).toEqual(['UL', 'LI', 'P']);
+ expect(getLeafContentsFromElement(doc)).toEqual(['Bulleted list']);
});
it('multiple bulleted lists markdown string("* \n* \n*") to "ul" and "li" HTML tags', () => {
@@ -162,12 +146,20 @@ describe('Markdown parser', () => {
'* Option 1\n * Option 2\n * Option 3'
);
- expect(
- getTagsFromDocumentFragment(doc as DocumentFragment)
- ).toEqual(['UL', 'LI', 'P', 'LI', 'P', 'LI', 'P']);
- expect(
- getLeafContentsFromDocumentFragment(doc as DocumentFragment)
- ).toEqual(['Option 1', 'Option 2', 'Option 3']);
+ expect(getTagsFromElement(doc)).toEqual([
+ 'UL',
+ 'LI',
+ 'P',
+ 'LI',
+ 'P',
+ 'LI',
+ 'P'
+ ]);
+ expect(getLeafContentsFromElement(doc)).toEqual([
+ 'Option 1',
+ 'Option 2',
+ 'Option 3'
+ ]);
});
it('bulleted lists if there is some content between lists', () => {
@@ -175,12 +167,16 @@ describe('Markdown parser', () => {
'* Option 1\n\nSome content in between lists\n\n * Option 2'
);
- expect(
- getTagsFromDocumentFragment(doc as DocumentFragment)
- ).toEqual(['UL', 'LI', 'P', 'P', 'UL', 'LI', 'P']);
- expect(
- getLeafContentsFromDocumentFragment(doc as DocumentFragment)
- ).toEqual([
+ expect(getTagsFromElement(doc)).toEqual([
+ 'UL',
+ 'LI',
+ 'P',
+ 'P',
+ 'UL',
+ 'LI',
+ 'P'
+ ]);
+ expect(getLeafContentsFromElement(doc)).toEqual([
'Option 1',
'Some content in between lists',
'Option 2'
@@ -192,12 +188,15 @@ describe('Markdown parser', () => {
'1. **Numbered list in bold**'
);
- expect(
- getTagsFromDocumentFragment(doc as DocumentFragment)
- ).toEqual(['OL', 'LI', 'P', 'STRONG']);
- expect(
- getLeafContentsFromDocumentFragment(doc as DocumentFragment)
- ).toEqual(['Numbered list in bold']);
+ expect(getTagsFromElement(doc)).toEqual([
+ 'OL',
+ 'LI',
+ 'P',
+ 'STRONG'
+ ]);
+ expect(getLeafContentsFromElement(doc)).toEqual([
+ 'Numbered list in bold'
+ ]);
});
it('bulleted list with italics markdown string to "ul", "li" and "em" HTML tags', () => {
@@ -205,12 +204,10 @@ describe('Markdown parser', () => {
'* *Bulleted list in italics*'
);
- expect(
- getTagsFromDocumentFragment(doc as DocumentFragment)
- ).toEqual(['UL', 'LI', 'P', 'EM']);
- expect(
- getLeafContentsFromDocumentFragment(doc as DocumentFragment)
- ).toEqual(['Bulleted list in italics']);
+ expect(getTagsFromElement(doc)).toEqual(['UL', 'LI', 'P', 'EM']);
+ expect(getLeafContentsFromElement(doc)).toEqual([
+ 'Bulleted list in italics'
+ ]);
});
it('combination of all supported markdown string', () => {
@@ -218,9 +215,7 @@ describe('Markdown parser', () => {
'1. ***Numbered list with bold and italics***\n* ___Bulleted list with bold and italics___'
);
- expect(
- getTagsFromDocumentFragment(doc as DocumentFragment)
- ).toEqual([
+ expect(getTagsFromElement(doc)).toEqual([
'OL',
'LI',
'P',
@@ -232,9 +227,7 @@ describe('Markdown parser', () => {
'EM',
'STRONG'
]);
- expect(
- getLeafContentsFromDocumentFragment(doc as DocumentFragment)
- ).toEqual([
+ expect(getLeafContentsFromElement(doc)).toEqual([
'Numbered list with bold and italics',
'Bulleted list with bold and italics'
]);
@@ -281,14 +274,10 @@ describe('Markdown parser', () => {
value.name
);
- expect(
- getTagsFromDocumentFragment(doc as DocumentFragment)
- ).toEqual(['P']);
- expect(
- getLeafContentsFromDocumentFragment(
- doc as DocumentFragment
- )
- ).toEqual([value.name]);
+ expect(getTagsFromElement(doc)).toEqual(['P']);
+ expect(getLeafContentsFromElement(doc)).toEqual([
+ value.name
+ ]);
}
);
}
@@ -314,14 +303,10 @@ describe('Markdown parser', () => {
value.name
);
- expect(
- getTagsFromDocumentFragment(doc as DocumentFragment)
- ).toEqual(['P']);
- expect(
- getLeafContentsFromDocumentFragment(
- doc as DocumentFragment
- )
- ).toEqual([value.name]);
+ expect(getTagsFromElement(doc)).toEqual(['P']);
+ expect(getLeafContentsFromElement(doc)).toEqual([
+ value.name
+ ]);
}
);
});
@@ -351,14 +336,10 @@ describe('Markdown parser', () => {
value.name
);
- expect(
- getTagsFromDocumentFragment(doc as DocumentFragment)
- ).toEqual(value.tags);
- expect(
- getLeafContentsFromDocumentFragment(
- doc as DocumentFragment
- )
- ).toEqual(value.textContent);
+ expect(getTagsFromElement(doc)).toEqual(value.tags);
+ expect(getLeafContentsFromElement(doc)).toEqual(
+ value.textContent
+ );
}
);
}
From 9969270d7abd877f53de4688b634f866b0d1d336 Mon Sep 17 00:00:00 2001
From: Vivin Krishna <123377523+vivinkrishna-ni@users.noreply.github.com>
Date: Fri, 8 Sep 2023 14:14:52 +0530
Subject: [PATCH 37/56] Removed mixable configuration from the serializer for
link mark
---
.../src/rich-text/models/markdown-serializer.ts | 1 -
1 file changed, 1 deletion(-)
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 61be5da5dd..53afa86875 100644
--- a/packages/nimble-components/src/rich-text/models/markdown-serializer.ts
+++ b/packages/nimble-components/src/rich-text/models/markdown-serializer.ts
@@ -56,7 +56,6 @@ export class RichTextMarkdownSerializer {
link: {
open: '<',
close: '>',
- mixable: true,
escape: false,
expelEnclosingWhitespace: true
}
From 78a9f4bb157c9b6d08a120aca7429cfaebdbc193 Mon Sep 17 00:00:00 2001
From: Vivin Krishna <123377523+vivinkrishna-ni@users.noreply.github.com>
Date: Sat, 9 Sep 2023 08:04:46 +0530
Subject: [PATCH 38/56] Change files
---
...le-components-21de2fc8-dfc6-4249-9098-d7842ce0eff3.json | 7 -------
...le-components-9f73b6f1-7567-4817-9f21-3c1c80bdd5c3.json | 7 +++++++
2 files changed, 7 insertions(+), 7 deletions(-)
delete mode 100644 change/@ni-nimble-components-21de2fc8-dfc6-4249-9098-d7842ce0eff3.json
create mode 100644 change/@ni-nimble-components-9f73b6f1-7567-4817-9f21-3c1c80bdd5c3.json
diff --git a/change/@ni-nimble-components-21de2fc8-dfc6-4249-9098-d7842ce0eff3.json b/change/@ni-nimble-components-21de2fc8-dfc6-4249-9098-d7842ce0eff3.json
deleted file mode 100644
index 7fbe260da7..0000000000
--- a/change/@ni-nimble-components-21de2fc8-dfc6-4249-9098-d7842ce0eff3.json
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "type": "none",
- "comment": "Refactored rich text components to use common models",
- "packageName": "@ni/nimble-components",
- "email": "123591928+saikrishnan-ni@users.noreply.github.com",
- "dependentChangeType": "none"
-}
diff --git a/change/@ni-nimble-components-9f73b6f1-7567-4817-9f21-3c1c80bdd5c3.json b/change/@ni-nimble-components-9f73b6f1-7567-4817-9f21-3c1c80bdd5c3.json
new file mode 100644
index 0000000000..3494da0727
--- /dev/null
+++ b/change/@ni-nimble-components-9f73b6f1-7567-4817-9f21-3c1c80bdd5c3.json
@@ -0,0 +1,7 @@
+{
+ "type": "patch",
+ "comment": "Support for absolute links in rich text components",
+ "packageName": "@ni/nimble-components",
+ "email": "123377523+vivinkrishna-ni@users.noreply.github.com",
+ "dependentChangeType": "patch"
+}
From 2ffd1fb5ab1becaac1889de9c823a36a527d4772 Mon Sep 17 00:00:00 2001
From: Vivin Krishna <123377523+vivinkrishna-ni@users.noreply.github.com>
Date: Mon, 11 Sep 2023 09:58:29 +0530
Subject: [PATCH 39/56] Resolving comments partially
---
.../src/rich-text/editor/index.ts | 4 +-
.../src/rich-text/editor/styles.ts | 12 ++---
.../testing/rich-text-editor.pageobject.ts | 6 +--
.../editor/tests/rich-text-editor.spec.ts | 44 ++++++++++++++-----
.../src/rich-text/models/markdown-parser.ts | 2 +-
.../models/testing/markdown-parser-utils.ts | 4 +-
6 files changed, 47 insertions(+), 25 deletions(-)
diff --git a/packages/nimble-components/src/rich-text/editor/index.ts b/packages/nimble-components/src/rich-text/editor/index.ts
index dceadbd357..407859d988 100644
--- a/packages/nimble-components/src/rich-text/editor/index.ts
+++ b/packages/nimble-components/src/rich-text/editor/index.ts
@@ -365,7 +365,7 @@ export class RichTextEditor extends FoundationElement implements ErrorPattern {
},
openOnClick: false,
linkOnPaste: false,
- validate: href => /^https?:\/\//.test(href)
+ validate: href => /^https?:\/\//i.test(href)
})
]
});
@@ -374,7 +374,7 @@ export class RichTextEditor extends FoundationElement implements ErrorPattern {
/**
* Extending the default link mark schema defined in the TipTap.
*
- * "exclude": https://prosemirror.net/docs/ref/#model.MarkSpec.excludes
+ * "excludes": https://prosemirror.net/docs/ref/#model.MarkSpec.excludes
* "inclusive": https://prosemirror.net/docs/ref/#model.MarkSpec.inclusive
* "parseHTML": https://tiptap.dev/guide/custom-extensions#parse-html
* "renderHTML": https://tiptap.dev/guide/custom-extensions/#render-html
diff --git a/packages/nimble-components/src/rich-text/editor/styles.ts b/packages/nimble-components/src/rich-text/editor/styles.ts
index b12ca76b5e..df5c537073 100644
--- a/packages/nimble-components/src/rich-text/editor/styles.ts
+++ b/packages/nimble-components/src/rich-text/editor/styles.ts
@@ -77,12 +77,6 @@ export const styles = css`
border: ${borderWidth} solid rgba(${borderRgbPartialColor}, 0.1);
}
- :host([disabled]) nimble-anchor::part(control) {
- color: ${bodyDisabledFontColor};
- fill: currentcolor;
- cursor: default;
- }
-
:host([error-visible]) .container {
border-bottom-color: ${failColor};
}
@@ -212,6 +206,12 @@ export const styles = css`
pointer-events: none;
}
+ :host([disabled]) nimble-anchor::part(control) {
+ color: ${bodyDisabledFontColor};
+ fill: currentcolor;
+ cursor: default;
+ }
+
.footer-section {
display: flex;
justify-content: space-between;
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
index 25c3745d1f..9b18cb4e77 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
@@ -112,7 +112,7 @@ export class RichTextEditorPageObject {
public async setEditorTextContent(value: string): Promise {
const lastElement = this.getEditorLastChildElement();
- lastElement!.parentElement!.textContent = value;
+ lastElement.parentElement!.textContent = value;
await waitForUpdatesAsync();
}
@@ -245,7 +245,7 @@ export class RichTextEditorPageObject {
return buttons[button];
}
- private getEditorLastChildElement(): Element | null | undefined {
- return getLastChildElement(this.getTiptapEditor() as HTMLElement);
+ private getEditorLastChildElement(): Element {
+ return getLastChildElement(this.getTiptapEditor() as HTMLElement)!;
}
}
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 6289ab8e45..44e327bf1e 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
@@ -644,18 +644,40 @@ describe('RichTextEditor', () => {
});
describe('Absolute link interactions in the editor', () => {
- it('should change the text to "nimble-anchor" tag when it is a valid absolute link', async () => {
- await pageObject.setEditorTextContent(
- 'https://nimble.ni.dev/ '
- );
+ describe('various absolute links without other nodes and marks', () => {
+ const supportedAbsoluteLink: { name: string }[] = [
+ { name: 'https://nimble.ni.dev/ ' },
+ { name: 'HTTPS://NIMBLE.NI.DEV ' },
+ { name: 'HttPS://NIMBLE.ni.DEV ' },
+ { name: 'http://nimble.ni.dev/ ' },
+ { name: 'HTTP://NIMBLE.NI.DEV ' },
+ { name: 'HttP://nimble.NI.dev ' },
+ ];
- expect(pageObject.getEditorTagNames()).toEqual([
- 'P',
- 'NIMBLE-ANCHOR'
- ]);
- expect(pageObject.getEditorLeafContents()).toEqual([
- 'https://nimble.ni.dev/'
- ]);
+ const focused: string[] = [];
+ const disabled: string[] = [];
+ for (const value of supportedAbsoluteLink) {
+ const specType = getSpecTypeByNamedList(
+ value,
+ focused,
+ disabled
+ );
+ specType(
+ `should change the ${value.name} to "nimble-anchor" tag when it is a valid absolute link`,
+ // eslint-disable-next-line @typescript-eslint/no-loop-func
+ async () => {
+ await pageObject.setEditorTextContent(value.name);
+
+ expect(pageObject.getEditorTagNames()).toEqual([
+ 'P',
+ 'NIMBLE-ANCHOR'
+ ]);
+ expect(pageObject.getEditorLeafContents()).toEqual([
+ value.name.slice(0, -1)
+ ]);
+ }
+ );
+ }
});
it('should have the right attributes to the nimble-anchor', async () => {
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 b137a1b6c6..32eb7d2d90 100644
--- a/packages/nimble-components/src/rich-text/models/markdown-parser.ts
+++ b/packages/nimble-components/src/rich-text/models/markdown-parser.ts
@@ -51,7 +51,7 @@ export class RichTextMarkdownParser {
'autolink'
]);
- supportedTokenizerRules.validateLink = href => /^https?:\/\//.test(href);
+ supportedTokenizerRules.validateLink = href => /^https?:\/\//i.test(href);
return new MarkdownParser(
this.updatedSchema,
diff --git a/packages/nimble-components/src/rich-text/models/testing/markdown-parser-utils.ts b/packages/nimble-components/src/rich-text/models/testing/markdown-parser-utils.ts
index 34e098abdd..71437195fc 100644
--- a/packages/nimble-components/src/rich-text/models/testing/markdown-parser-utils.ts
+++ b/packages/nimble-components/src/rich-text/models/testing/markdown-parser-utils.ts
@@ -20,7 +20,7 @@ export const getLastChildElementAttribute = (
attribute: string,
doc: DocumentFragment | HTMLElement
): string => {
- return getLastChildElement(doc)?.getAttribute(attribute) || '';
+ return getLastChildElement(doc)?.getAttribute(attribute) ?? '';
};
export function getLastChildElement(
@@ -29,7 +29,7 @@ export function getLastChildElement(
let lastElement = doc.lastElementChild;
while (lastElement?.lastElementChild) {
- lastElement = lastElement?.lastElementChild;
+ lastElement = lastElement.lastElementChild;
}
return lastElement;
}
From 946b41866aba7c5abe26ea40998d1d11c306ceca Mon Sep 17 00:00:00 2001
From: Vivin Krishna <123377523+vivinkrishna-ni@users.noreply.github.com>
Date: Mon, 11 Sep 2023 10:21:00 +0530
Subject: [PATCH 40/56] Fix lint
---
.../src/rich-text/editor/tests/rich-text-editor.spec.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
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 44e327bf1e..a1e1917219 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
@@ -651,7 +651,7 @@ describe('RichTextEditor', () => {
{ name: 'HttPS://NIMBLE.ni.DEV ' },
{ name: 'http://nimble.ni.dev/ ' },
{ name: 'HTTP://NIMBLE.NI.DEV ' },
- { name: 'HttP://nimble.NI.dev ' },
+ { name: 'HttP://nimble.NI.dev ' }
];
const focused: string[] = [];
From 42c7b6816b60dfa8d173102f5a603e20bb36e491 Mon Sep 17 00:00:00 2001
From: Vivin Krishna <123377523+vivinkrishna-ni@users.noreply.github.com>
Date: Mon, 11 Sep 2023 14:54:41 +0530
Subject: [PATCH 41/56] Rendering "a" tag in place of "nimble-anchor" in
editor.
---
.../src/rich-text/editor/index.ts | 6 ++-
.../src/rich-text/editor/styles.ts | 8 ++--
.../editor/tests/rich-text-editor.spec.ts | 42 +++++++++----------
3 files changed, 29 insertions(+), 27 deletions(-)
diff --git a/packages/nimble-components/src/rich-text/editor/index.ts b/packages/nimble-components/src/rich-text/editor/index.ts
index 407859d988..c8bae6b3ac 100644
--- a/packages/nimble-components/src/rich-text/editor/index.ts
+++ b/packages/nimble-components/src/rich-text/editor/index.ts
@@ -363,6 +363,7 @@ export class RichTextEditor extends FoundationElement implements ErrorPattern {
rel: 'noopener noreferrer',
target: null
},
+ autolink: true,
openOnClick: false,
linkOnPaste: false,
validate: href => /^https?:\/\//i.test(href)
@@ -389,8 +390,9 @@ export class RichTextEditor extends FoundationElement implements ErrorPattern {
// HTMLAttribute cannot be in camelCase as we want to match it with the name in Tiptap
// eslint-disable-next-line @typescript-eslint/naming-convention
renderHTML({ HTMLAttributes }) {
- HTMLAttributes.tabindex = '-1';
- return [anchorTag, HTMLAttributes];
+ // The below 'a' tag should be replaced with 'nimble-anchor' once the below issue is fixed.
+ // https://github.com/ni/nimble/issues/1502
+ return ['a', HTMLAttributes];
}
});
}
diff --git a/packages/nimble-components/src/rich-text/editor/styles.ts b/packages/nimble-components/src/rich-text/editor/styles.ts
index df5c537073..e236bb34ec 100644
--- a/packages/nimble-components/src/rich-text/editor/styles.ts
+++ b/packages/nimble-components/src/rich-text/editor/styles.ts
@@ -12,7 +12,8 @@ import {
failColor,
iconSize,
smallDelay,
- standardPadding
+ standardPadding,
+ linkFontColor
} from '../../theme-provider/design-tokens';
import { styles as errorStyles } from '../../patterns/error/styles';
@@ -188,12 +189,13 @@ export const styles = css`
color: ${controlLabelDisabledFontColor};
}
- nimble-anchor {
+ .ProseMirror a {
+ color: ${linkFontColor};
white-space: normal;
${
/**
* Restricting the pointer events for the following reasons:
- * 1. nimble-anchor inside a "contenteditable" div is not working as native HTML anchor tag.
+ * 1. If rendering 'nimble-anchor' inside a "contenteditable" div is not working as native HTML anchor tag.
* i.e. clicking on the link opens in the same tab whereas the default behavior of native HTML anchor
* tag is not clickable inside "contenteditable" div.
* Issue link: https://github.com/ni/nimble/issues/1502
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 a1e1917219..cda74e7818 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
@@ -663,14 +663,14 @@ describe('RichTextEditor', () => {
disabled
);
specType(
- `should change the ${value.name} to "nimble-anchor" tag when it is a valid absolute link`,
+ `should change the ${value.name} to "a" tag when it is a valid absolute link`,
// eslint-disable-next-line @typescript-eslint/no-loop-func
async () => {
await pageObject.setEditorTextContent(value.name);
expect(pageObject.getEditorTagNames()).toEqual([
'P',
- 'NIMBLE-ANCHOR'
+ 'A'
]);
expect(pageObject.getEditorLeafContents()).toEqual([
value.name.slice(0, -1)
@@ -680,7 +680,7 @@ describe('RichTextEditor', () => {
}
});
- it('should have the right attributes to the nimble-anchor', async () => {
+ it('should have the right attributes to the "a" tag', async () => {
await pageObject.setEditorTextContent(
'https://nimble.ni.dev/ '
);
@@ -691,9 +691,6 @@ describe('RichTextEditor', () => {
expect(pageObject.getEditorLastChildAttribute('rel')).toBe(
'noopener noreferrer'
);
- expect(pageObject.getEditorLastChildAttribute('tabindex')).toBe(
- '-1'
- );
});
it('should not affect bold formatting on the link in editor', async () => {
@@ -704,8 +701,8 @@ describe('RichTextEditor', () => {
expect(pageObject.getEditorTagNamesWithClosingTags()).toEqual([
'P',
- 'NIMBLE-ANCHOR',
- '/NIMBLE-ANCHOR',
+ 'A',
+ '/A',
'STRONG',
'/STRONG',
'/P'
@@ -724,8 +721,8 @@ describe('RichTextEditor', () => {
expect(pageObject.getEditorTagNamesWithClosingTags()).toEqual([
'P',
- 'NIMBLE-ANCHOR',
- '/NIMBLE-ANCHOR',
+ 'A',
+ '/A',
'EM',
'/EM',
'/P'
@@ -746,7 +743,7 @@ describe('RichTextEditor', () => {
'UL',
'LI',
'P',
- 'NIMBLE-ANCHOR'
+ 'A'
]);
expect(pageObject.getEditorLeafContents()).toEqual([
'https://nimble.ni.dev/'
@@ -763,7 +760,7 @@ describe('RichTextEditor', () => {
'OL',
'LI',
'P',
- 'NIMBLE-ANCHOR'
+ 'A'
]);
expect(pageObject.getEditorLeafContents()).toEqual([
'https://nimble.ni.dev/'
@@ -774,6 +771,7 @@ describe('RichTextEditor', () => {
const notSupportedAbsoluteLink: { name: string }[] = [
{ name: 'ftp://example.com/files/document.pdf ' },
{ name: 'mailto:info@example.com ' },
+ { name: 'info@example.com ' },
{ name: 'file:///path/to/local/file.txt ' },
{ name: 'tel:+1234567890 ' },
// eslint-disable-next-line no-script-url
@@ -865,12 +863,12 @@ describe('RichTextEditor', () => {
describe('Absolute link markdown tests', () => {
describe('asserting rendered links in the editor', () => {
- it('absolute link markdown string to "nimble-anchor" tags with the link as the text content', () => {
+ it('absolute link markdown string to "a" tags with the link as the text content', () => {
element.setMarkdown(' ');
expect(pageObject.getEditorTagNames()).toEqual([
'P',
- 'NIMBLE-ANCHOR'
+ 'A'
]);
expect(pageObject.getEditorLeafContents()).toEqual([
'https://nimble.ni.dev/'
@@ -880,14 +878,14 @@ describe('RichTextEditor', () => {
);
});
- it('bulleted list with absolute links markdown string to "ul", "li" and "nimble-anchor" tags', () => {
+ it('bulleted list with absolute links markdown string to "ul", "li" and "a" tags', () => {
element.setMarkdown('* ');
expect(pageObject.getEditorTagNames()).toEqual([
'UL',
'LI',
'P',
- 'NIMBLE-ANCHOR'
+ 'A'
]);
expect(pageObject.getEditorLeafContents()).toEqual([
'https://nimble.ni.dev/'
@@ -897,14 +895,14 @@ describe('RichTextEditor', () => {
);
});
- it('numbered list with absolute links markdown string to "ol", "li" and "nimble-anchor" tags', () => {
+ it('numbered list with absolute links markdown string to "ol", "li" and "a" tags', () => {
element.setMarkdown('1. ');
expect(pageObject.getEditorTagNames()).toEqual([
'OL',
'LI',
'P',
- 'NIMBLE-ANCHOR'
+ 'A'
]);
expect(pageObject.getEditorLeafContents()).toEqual([
'https://nimble.ni.dev/'
@@ -919,7 +917,7 @@ describe('RichTextEditor', () => {
expect(pageObject.getEditorTagNames()).toEqual([
'P',
- 'NIMBLE-ANCHOR'
+ 'A'
]);
expect(pageObject.getEditorLeafContents()).toEqual([
'https://nimble.ni.dev/'
@@ -934,7 +932,7 @@ describe('RichTextEditor', () => {
expect(pageObject.getEditorTagNames()).toEqual([
'P',
- 'NIMBLE-ANCHOR'
+ 'A'
]);
expect(pageObject.getEditorLeafContents()).toEqual([
'https://nimble.ni.dev/'
@@ -949,7 +947,7 @@ describe('RichTextEditor', () => {
expect(pageObject.getEditorTagNames()).toEqual([
'P',
- 'NIMBLE-ANCHOR'
+ 'A'
]);
expect(pageObject.getEditorLeafContents()).toEqual([
'https://nimble.ni.dev/'
@@ -964,7 +962,7 @@ describe('RichTextEditor', () => {
expect(pageObject.getEditorTagNames()).toEqual([
'P',
- 'NIMBLE-ANCHOR'
+ 'A'
]);
expect(pageObject.getEditorLeafContents()).toEqual([
'https://**nimble**.ni.dev/'
From 8d3d81e7e1cc635374a1ade5b09229fe9706ca5a Mon Sep 17 00:00:00 2001
From: Vivin Krishna <123377523+vivinkrishna-ni@users.noreply.github.com>
Date: Mon, 11 Sep 2023 15:15:22 +0530
Subject: [PATCH 42/56] Fix lint
---
.../editor/tests/rich-text-editor.spec.ts | 25 ++++---------------
1 file changed, 5 insertions(+), 20 deletions(-)
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 cda74e7818..46cc57130e 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
@@ -866,10 +866,7 @@ describe('RichTextEditor', () => {
it('absolute link markdown string to "a" tags with the link as the text content', () => {
element.setMarkdown(' ');
- expect(pageObject.getEditorTagNames()).toEqual([
- 'P',
- 'A'
- ]);
+ expect(pageObject.getEditorTagNames()).toEqual(['P', 'A']);
expect(pageObject.getEditorLeafContents()).toEqual([
'https://nimble.ni.dev/'
]);
@@ -915,10 +912,7 @@ describe('RichTextEditor', () => {
it('absolute links in bold markdown string should not be parsed to "strong" tag', () => {
element.setMarkdown('** **');
- expect(pageObject.getEditorTagNames()).toEqual([
- 'P',
- 'A'
- ]);
+ expect(pageObject.getEditorTagNames()).toEqual(['P', 'A']);
expect(pageObject.getEditorLeafContents()).toEqual([
'https://nimble.ni.dev/'
]);
@@ -930,10 +924,7 @@ describe('RichTextEditor', () => {
it('absolute links in italics markdown string should not be parsed to "em" tag', () => {
element.setMarkdown('* *');
- expect(pageObject.getEditorTagNames()).toEqual([
- 'P',
- 'A'
- ]);
+ expect(pageObject.getEditorTagNames()).toEqual(['P', 'A']);
expect(pageObject.getEditorLeafContents()).toEqual([
'https://nimble.ni.dev/'
]);
@@ -945,10 +936,7 @@ describe('RichTextEditor', () => {
it('absolute links in both bold and italics markdown string should not be parsed to "strong" and "em" tag', () => {
element.setMarkdown('___ ___');
- expect(pageObject.getEditorTagNames()).toEqual([
- 'P',
- 'A'
- ]);
+ expect(pageObject.getEditorTagNames()).toEqual(['P', 'A']);
expect(pageObject.getEditorLeafContents()).toEqual([
'https://nimble.ni.dev/'
]);
@@ -960,10 +948,7 @@ describe('RichTextEditor', () => {
it('adding marks like bold inside absolute links should not be parsed to "strong" tag', () => {
element.setMarkdown(' ');
- expect(pageObject.getEditorTagNames()).toEqual([
- 'P',
- 'A'
- ]);
+ expect(pageObject.getEditorTagNames()).toEqual(['P', 'A']);
expect(pageObject.getEditorLeafContents()).toEqual([
'https://**nimble**.ni.dev/'
]);
From b625927115776a48782ed46bdb7ffc7e7fe28441 Mon Sep 17 00:00:00 2001
From: Vivin Krishna <123377523+vivinkrishna-ni@users.noreply.github.com>
Date: Tue, 12 Sep 2023 09:38:16 +0530
Subject: [PATCH 43/56] Updates to linking styles
---
.../src/rich-text/editor/styles.ts | 15 +++++----------
1 file changed, 5 insertions(+), 10 deletions(-)
diff --git a/packages/nimble-components/src/rich-text/editor/styles.ts b/packages/nimble-components/src/rich-text/editor/styles.ts
index e236bb34ec..647203eee2 100644
--- a/packages/nimble-components/src/rich-text/editor/styles.ts
+++ b/packages/nimble-components/src/rich-text/editor/styles.ts
@@ -194,21 +194,16 @@ export const styles = css`
white-space: normal;
${
/**
- * Restricting the pointer events for the following reasons:
- * 1. If rendering 'nimble-anchor' inside a "contenteditable" div is not working as native HTML anchor tag.
- * i.e. clicking on the link opens in the same tab whereas the default behavior of native HTML anchor
- * tag is not clickable inside "contenteditable" div.
- * Issue link: https://github.com/ni/nimble/issues/1502
- * 2. Restricting the user from opening a link using the right-click context menu: If the user manually edits
- * the link's text content, the 'href' attribute of the anchor tag will not be updated. If they attempt to open it using
- * the right-click context menu with 'Open in new tab/window,' it will still navigate to the link specified
- * in the 'href' attribute, which may create unnecessary confusion while trying to open the link
+ * Restricting the user from opening a link using the right-click context menu: If the user manually edits
+ * the link's text content, the 'href' attribute of the anchor tag will not be updated. If they attempt to open it using
+ * the right-click context menu with 'Open in new tab/window,' it will still navigate to the link specified
+ * in the 'href' attribute, which may create unnecessary confusion while trying to open the link.
*/ ''
}
pointer-events: none;
}
- :host([disabled]) nimble-anchor::part(control) {
+ :host([disabled]) .ProseMirror a {
color: ${bodyDisabledFontColor};
fill: currentcolor;
cursor: default;
From 1c3cbb487b89e34db7d87e908e2f12494ce18b58 Mon Sep 17 00:00:00 2001
From: Vivin Krishna <123377523+vivinkrishna-ni@users.noreply.github.com>
Date: Tue, 12 Sep 2023 09:42:41 +0530
Subject: [PATCH 44/56] removing heights from the storybook docs
---
.../rich-text/editor/tests/rich-text-editor-matrix.stories.ts | 2 +-
.../src/rich-text/editor/tests/rich-text-editor.stories.ts | 1 -
2 files changed, 1 insertion(+), 2 deletions(-)
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
index b3a08047bb..b847c05c73 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
@@ -63,7 +63,7 @@ const component = (
${() => footerHiddenName} ${() => errorStateName} ${() => placeholderName} ${() => disabledName}
<${richTextEditorTag}
- style="margin: 5px 0px; width: 500px; height: 100px"
+ style="margin: 5px 0px; width: 500px;"
?disabled="${() => disabled}"
?footer-hidden="${() => footerHidden}"
?error-visible="${() => isError}"
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
index 9d9a42f3f7..9418f4cb2c 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
@@ -76,7 +76,6 @@ const metadata: Meta = {
})}
<${richTextEditorTag}
${ref('editorRef')}
- style="height: 160px"
data-unused="${x => x.setMarkdownData(x)}"
?disabled="${x => x.disabled}"
?footer-hidden="${x => x.footerHidden}"
From 676e4ac32e8e15d1e55fe08015b2de8a76e6507d Mon Sep 17 00:00:00 2001
From: Vivin Krishna <123377523+vivinkrishna-ni@users.noreply.github.com>
Date: Tue, 12 Sep 2023 15:58:33 +0530
Subject: [PATCH 45/56] Normalization of link texts to render encoded and
non-ascii character URLs
---
.../src/rich-text/models/markdown-parser.ts | 5 +
.../models/tests/markdown-parser.spec.ts | 188 ++++++++++++++++--
2 files changed, 180 insertions(+), 13 deletions(-)
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 32eb7d2d90..3767b3ced0 100644
--- a/packages/nimble-components/src/rich-text/models/markdown-parser.ts
+++ b/packages/nimble-components/src/rich-text/models/markdown-parser.ts
@@ -53,6 +53,11 @@ export class RichTextMarkdownParser {
supportedTokenizerRules.validateLink = href => /^https?:\/\//i.test(href);
+ // To achieve rendering the encoded characters, non-ASCII characters, emojis, etc., as they are,
+ // bypassing the default normalization of link text in markdown-it because we only support "AutoLink" in markdown format.
+ // "normalizeLinkText" method reference in markdown-it: https://github.com/markdown-it/markdown-it/blob/2b6cac25823af011ff3bc7628bc9b06e483c5a08/lib/index.js#L67C1-L86C2
+ supportedTokenizerRules.normalizeLinkText = url => url;
+
return new MarkdownParser(
this.updatedSchema,
supportedTokenizerRules,
diff --git a/packages/nimble-components/src/rich-text/models/tests/markdown-parser.spec.ts b/packages/nimble-components/src/rich-text/models/tests/markdown-parser.spec.ts
index 3dd147d093..aabae51244 100644
--- a/packages/nimble-components/src/rich-text/models/tests/markdown-parser.spec.ts
+++ b/packages/nimble-components/src/rich-text/models/tests/markdown-parser.spec.ts
@@ -212,18 +212,180 @@ describe('Markdown parser', () => {
});
describe('Absolute link', () => {
- it('absolute link markdown string to "nimble-anchor" tags with the link as the text content', () => {
- const doc = RichTextMarkdownParser.parseMarkdownToDOM(
- ' '
- );
+ describe('various valid absolute links should render same as in the markdown', () => {
+ const supportedAbsoluteLink: {
+ name: string,
+ validLink: string
+ }[] = [
+ {
+ name: 'Lowercase HTTPS URL',
+ validLink: ' '
+ },
+ {
+ name: 'Uppercase HTTPS URL',
+ validLink: ''
+ },
+ {
+ name: 'Mixed case HTTPS URL',
+ validLink: ''
+ },
+ {
+ name: 'Lowercase HTTP URL',
+ validLink: ' '
+ },
+ {
+ name: 'Uppercase HTTP URL',
+ validLink: ''
+ },
+ {
+ name: 'Mixed case HTTP URL',
+ validLink: ''
+ },
+ {
+ name: 'URL with query params & special characters',
+ validLink:
+ ''
+ },
+ {
+ name: 'Whitespace encoded URL',
+ validLink: ''
+ },
+ {
+ name: 'Question mark encoded URL',
+ validLink:
+ ''
+ },
+ {
+ name: 'Emoji encoded URL',
+ validLink:
+ ''
+ },
+ {
+ name: 'Ampersand encoded URL',
+ validLink:
+ ''
+ },
+ {
+ name: 'Non-latin encoded URL',
+ validLink:
+ ''
+ },
+ {
+ name: 'URL with Fragment Identifier',
+ validLink: ''
+ },
+ {
+ name: 'URL with Port Number',
+ validLink: ''
+ }
+ ];
- expect(getTagsFromElement(doc)).toEqual(['P', 'NIMBLE-ANCHOR']);
- expect(getLeafContentsFromElement(doc)).toEqual([
- 'https://nimble.ni.dev/'
- ]);
- expect(getLastChildElementAttribute('href', doc)).toBe(
- 'https://nimble.ni.dev/'
- );
+ const focused: string[] = [];
+ const disabled: string[] = [];
+ for (const value of supportedAbsoluteLink) {
+ const specType = getSpecTypeByNamedList(
+ value,
+ focused,
+ disabled
+ );
+ specType(
+ `${value.name} to "nimble-anchor" tags with the link as the text content`,
+ // eslint-disable-next-line @typescript-eslint/no-loop-func
+ () => {
+ const doc = RichTextMarkdownParser.parseMarkdownToDOM(
+ value.validLink
+ );
+ const renderedLink = value.validLink.slice(1, -1);
+
+ expect(getTagsFromElement(doc)).toEqual([
+ 'P',
+ 'NIMBLE-ANCHOR'
+ ]);
+ expect(getLeafContentsFromElement(doc)).toEqual([
+ renderedLink
+ ]);
+ expect(
+ getLastChildElementAttribute('href', doc)
+ ).toBe(renderedLink);
+ }
+ );
+ }
+ });
+
+ describe('various absolute links with non-ASCII (IRI) characters within it', () => {
+ const supportedAbsoluteLink: {
+ name: string,
+ validLink: string,
+ encodeURL: string
+ }[] = [
+ {
+ name: 'Emoji',
+ validLink: '',
+ encodeURL: 'https://example.com/smiley%F0%9F%98%80.html'
+ },
+ {
+ name: 'Basic IRI characters',
+ validLink: '',
+ encodeURL: 'https://example.com/%C3%A9l%C3%A8ve.html'
+ },
+ {
+ name: 'Non-Latin Scripts',
+ validLink: '',
+ encodeURL:
+ 'https://example.com/%D0%BF%D1%80%D0%B8%D0%BC%D0%B5%D1%80.html'
+ },
+ {
+ name: 'Math symbols',
+ validLink: '',
+ encodeURL: 'https://example.com/%E2%88%9A2.html'
+ },
+ {
+ name: 'Special symbols',
+ validLink: '',
+ encodeURL: 'https://example.com/%E2%99%A5-music.html'
+ },
+ {
+ name: 'Accented Characters',
+ validLink: '',
+ encodeURL: 'https://example.com/espa%C3%B1a.html'
+ },
+ {
+ name: 'Japanese Characters',
+ validLink: '',
+ encodeURL: 'https://example.com/%E6%9D%B1%E4%BA%AC.html'
+ }
+ ];
+
+ const focused: string[] = [];
+ const disabled: string[] = [];
+ for (const value of supportedAbsoluteLink) {
+ const specType = getSpecTypeByNamedList(
+ value,
+ focused,
+ disabled
+ );
+ specType(
+ `${value.name} to "nimble-anchor" tags with the non-ASCII characters as the text content and encoded as their href`,
+ // eslint-disable-next-line @typescript-eslint/no-loop-func
+ () => {
+ const doc = RichTextMarkdownParser.parseMarkdownToDOM(
+ value.validLink
+ );
+ const renderedLink = value.validLink.slice(1, -1);
+
+ expect(getTagsFromElement(doc)).toEqual([
+ 'P',
+ 'NIMBLE-ANCHOR'
+ ]);
+ expect(getLeafContentsFromElement(doc)).toEqual([
+ renderedLink
+ ]);
+ expect(
+ getLastChildElementAttribute('href', doc)
+ ).toBe(value.encodeURL);
+ }
+ );
+ }
});
it('absolute link should add "rel" attribute', () => {
@@ -331,7 +493,7 @@ describe('Markdown parser', () => {
});
describe('various absolute links with different schemas other than https/http should be render as unchanged strings', () => {
- const notSupportedAbsoluteLink: { name: string }[] = [
+ const differentProtocolLinks: { name: string }[] = [
{ name: '' },
{ name: '' },
{ name: '' },
@@ -353,7 +515,7 @@ describe('Markdown parser', () => {
const focused: string[] = [];
const disabled: string[] = [];
- for (const value of notSupportedAbsoluteLink) {
+ for (const value of differentProtocolLinks) {
const specType = getSpecTypeByNamedList(
value,
focused,
From 282f22b1edc8d0fb765c7d801681bd8606bdb0ad Mon Sep 17 00:00:00 2001
From: Vivin Krishna <123377523+vivinkrishna-ni@users.noreply.github.com>
Date: Tue, 12 Sep 2023 17:24:01 +0530
Subject: [PATCH 46/56] Added few more link tests
---
.../models/tests/markdown-parser.spec.ts | 91 +++++++++++++++----
1 file changed, 74 insertions(+), 17 deletions(-)
diff --git a/packages/nimble-components/src/rich-text/models/tests/markdown-parser.spec.ts b/packages/nimble-components/src/rich-text/models/tests/markdown-parser.spec.ts
index aabae51244..438a8875c9 100644
--- a/packages/nimble-components/src/rich-text/models/tests/markdown-parser.spec.ts
+++ b/packages/nimble-components/src/rich-text/models/tests/markdown-parser.spec.ts
@@ -242,9 +242,9 @@ describe('Markdown parser', () => {
validLink: ''
},
{
- name: 'URL with query params & special characters',
+ name: 'URL with reserved characters',
validLink:
- ''
+ ''
},
{
name: 'Whitespace encoded URL',
@@ -274,6 +274,10 @@ describe('Markdown parser', () => {
name: 'URL with Fragment Identifier',
validLink: ''
},
+ {
+ name: 'URL with marks',
+ validLink: ''
+ },
{
name: 'URL with Port Number',
validLink: ''
@@ -323,6 +327,36 @@ describe('Markdown parser', () => {
validLink: '',
encodeURL: 'https://example.com/smiley%F0%9F%98%80.html'
},
+ {
+ name: 'Square brackets',
+ validLink: '',
+ encodeURL: 'https://example.com/%5Bpage%5D/index.html'
+ },
+ {
+ name: 'Backslashes',
+ validLink: '',
+ encodeURL: 'https://example.com%5Cpath%5Cto%5Cresource'
+ },
+ {
+ name: 'Open and close braces',
+ validLink: '',
+ encodeURL: 'https://example.com/%7Bpage%7D/index.html'
+ },
+ {
+ name: 'Pipe',
+ validLink: '',
+ encodeURL: 'https://example.com/page%7C/index.html'
+ },
+ {
+ name: 'Caret',
+ validLink: '',
+ encodeURL: 'https://example.com/page%5E/index.html'
+ },
+ {
+ name: 'Percent',
+ validLink: '',
+ encodeURL: 'https://example.com/page%25/index.html'
+ },
{
name: 'Basic IRI characters',
validLink: '',
@@ -478,20 +512,6 @@ describe('Markdown parser', () => {
);
});
- it('adding marks like bold inside absolute links should not be parsed to "strong" HTML tag', () => {
- const doc = RichTextMarkdownParser.parseMarkdownToDOM(
- ' '
- );
-
- expect(getTagsFromElement(doc)).toEqual(['P', 'NIMBLE-ANCHOR']);
- expect(getLeafContentsFromElement(doc)).toEqual([
- 'https://**nimble**.ni.dev/'
- ]);
- expect(getLastChildElementAttribute('href', doc)).toBe(
- 'https://**nimble**.ni.dev/'
- );
- });
-
describe('various absolute links with different schemas other than https/http should be render as unchanged strings', () => {
const differentProtocolLinks: { name: string }[] = [
{ name: '' },
@@ -522,7 +542,44 @@ describe('Markdown parser', () => {
disabled
);
specType(
- `string "${value.name}" renders as plain text "${value.name}" within paragraph tag`,
+ `string "${value.name}" renders as plain text within paragraph tag`,
+ // eslint-disable-next-line @typescript-eslint/no-loop-func
+ () => {
+ const doc = RichTextMarkdownParser.parseMarkdownToDOM(
+ value.name
+ );
+
+ expect(getTagsFromElement(doc)).toEqual(['P']);
+ expect(getLeafContentsFromElement(doc)).toEqual([
+ value.name
+ ]);
+ }
+ );
+ }
+ });
+
+ describe('various unsafe characters in an absolute link', () => {
+ const notSupportedAbsoluteLink: {
+ name: string
+ }[] = [
+ { name: '>' },
+ { name: ' ' },
+ { name: 'http://www.example.com/' },
+ { name: '' },
+ { name: '' },
+ { name: ' {
const doc = RichTextMarkdownParser.parseMarkdownToDOM(
From 2eba73d25e3534eb70254d77268c772aa7aed073 Mon Sep 17 00:00:00 2001
From: Vivin Krishna <123377523+vivinkrishna-ni@users.noreply.github.com>
Date: Tue, 12 Sep 2023 18:26:36 +0530
Subject: [PATCH 47/56] Minor code changes
---
.../nimble-components/src/rich-text/editor/index.ts | 2 +-
.../nimble-components/src/rich-text/editor/styles.ts | 2 +-
.../rich-text/editor/tests/rich-text-editor.spec.ts | 12 ++++++++----
.../src/rich-text/models/markdown-parser.ts | 4 ++--
.../rich-text/models/tests/markdown-parser.spec.ts | 2 +-
5 files changed, 13 insertions(+), 9 deletions(-)
diff --git a/packages/nimble-components/src/rich-text/editor/index.ts b/packages/nimble-components/src/rich-text/editor/index.ts
index c8bae6b3ac..0d9d5e71ba 100644
--- a/packages/nimble-components/src/rich-text/editor/index.ts
+++ b/packages/nimble-components/src/rich-text/editor/index.ts
@@ -391,7 +391,7 @@ export class RichTextEditor extends FoundationElement implements ErrorPattern {
// eslint-disable-next-line @typescript-eslint/naming-convention
renderHTML({ HTMLAttributes }) {
// The below 'a' tag should be replaced with 'nimble-anchor' once the below issue is fixed.
- // https://github.com/ni/nimble/issues/1502
+ // https://github.com/ni/nimble/issues/1516
return ['a', HTMLAttributes];
}
});
diff --git a/packages/nimble-components/src/rich-text/editor/styles.ts b/packages/nimble-components/src/rich-text/editor/styles.ts
index 647203eee2..306448b40e 100644
--- a/packages/nimble-components/src/rich-text/editor/styles.ts
+++ b/packages/nimble-components/src/rich-text/editor/styles.ts
@@ -194,7 +194,7 @@ export const styles = css`
white-space: normal;
${
/**
- * Restricting the user from opening a link using the right-click context menu: If the user manually edits
+ * Setting 'pointer-events: none;' to restrict the user from opening a link using the right-click context menu: If the user manually edits
* the link's text content, the 'href' attribute of the anchor tag will not be updated. If they attempt to open it using
* the right-click context menu with 'Open in new tab/window,' it will still navigate to the link specified
* in the 'href' attribute, which may create unnecessary confusion while trying to open the link.
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 46cc57130e..c5d91da4eb 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
@@ -651,7 +651,11 @@ describe('RichTextEditor', () => {
{ name: 'HttPS://NIMBLE.ni.DEV ' },
{ name: 'http://nimble.ni.dev/ ' },
{ name: 'HTTP://NIMBLE.NI.DEV ' },
- { name: 'HttP://nimble.NI.dev ' }
+ { name: 'HttP://nimble.NI.dev ' },
+ { name: 'https://www.example.com/path/equals=ampersand&question?dollar$plus+comma,At@semicolon; ' },
+ { name: 'https://example.com/my%20page.html ' },
+ { name: 'https://example.com/smiley😀.html ' },
+ { name: 'https://example.com/пример.html ' },
];
const focused: string[] = [];
@@ -767,8 +771,8 @@ describe('RichTextEditor', () => {
]);
});
- describe('various absolute links with different schemas other than https/http should be render as unchanged strings', () => {
- const notSupportedAbsoluteLink: { name: string }[] = [
+ describe('various absolute links with different protocols other than https/http should be render as unchanged strings', () => {
+ const differentProtocolLinks: { name: string }[] = [
{ name: 'ftp://example.com/files/document.pdf ' },
{ name: 'mailto:info@example.com ' },
{ name: 'info@example.com ' },
@@ -793,7 +797,7 @@ describe('RichTextEditor', () => {
const focused: string[] = [];
const disabled: string[] = [];
- for (const value of notSupportedAbsoluteLink) {
+ for (const value of differentProtocolLinks) {
const specType = getSpecTypeByNamedList(
value,
focused,
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 3767b3ced0..34a95d0345 100644
--- a/packages/nimble-components/src/rich-text/models/markdown-parser.ts
+++ b/packages/nimble-components/src/rich-text/models/markdown-parser.ts
@@ -53,8 +53,8 @@ export class RichTextMarkdownParser {
supportedTokenizerRules.validateLink = href => /^https?:\/\//i.test(href);
- // To achieve rendering the encoded characters, non-ASCII characters, emojis, etc., as they are,
- // bypassing the default normalization of link text in markdown-it because we only support "AutoLink" in markdown format.
+ // In order to display encoded characters, non-ASCII characters, emojis, and other special characters in their original form,
+ // we bypass the default normalization of link text in markdown-it. This is done because we support only "AutoLink" feature in CommonMark flavor.
// "normalizeLinkText" method reference in markdown-it: https://github.com/markdown-it/markdown-it/blob/2b6cac25823af011ff3bc7628bc9b06e483c5a08/lib/index.js#L67C1-L86C2
supportedTokenizerRules.normalizeLinkText = url => url;
diff --git a/packages/nimble-components/src/rich-text/models/tests/markdown-parser.spec.ts b/packages/nimble-components/src/rich-text/models/tests/markdown-parser.spec.ts
index 438a8875c9..6c31bf7cf6 100644
--- a/packages/nimble-components/src/rich-text/models/tests/markdown-parser.spec.ts
+++ b/packages/nimble-components/src/rich-text/models/tests/markdown-parser.spec.ts
@@ -512,7 +512,7 @@ describe('Markdown parser', () => {
);
});
- describe('various absolute links with different schemas other than https/http should be render as unchanged strings', () => {
+ describe('various absolute links with different protocols other than https/http should be render as unchanged strings', () => {
const differentProtocolLinks: { name: string }[] = [
{ name: '' },
{ name: '' },
From 597de3cb771b22bafdf243ca7c3984d592529421 Mon Sep 17 00:00:00 2001
From: Vivin Krishna <123377523+vivinkrishna-ni@users.noreply.github.com>
Date: Tue, 12 Sep 2023 18:31:14 +0530
Subject: [PATCH 48/56] Fix lint
---
.../src/rich-text/editor/tests/rich-text-editor.spec.ts | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
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 c5d91da4eb..09d242e917 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
@@ -652,10 +652,12 @@ describe('RichTextEditor', () => {
{ name: 'http://nimble.ni.dev/ ' },
{ name: 'HTTP://NIMBLE.NI.DEV ' },
{ name: 'HttP://nimble.NI.dev ' },
- { name: 'https://www.example.com/path/equals=ampersand&question?dollar$plus+comma,At@semicolon; ' },
+ {
+ name: 'https://www.example.com/path/equals=ampersand&question?dollar$plus+comma,At@semicolon; '
+ },
{ name: 'https://example.com/my%20page.html ' },
{ name: 'https://example.com/smiley😀.html ' },
- { name: 'https://example.com/пример.html ' },
+ { name: 'https://example.com/пример.html ' }
];
const focused: string[] = [];
From 38739e398bff4784a808de601bc279d186c6420b Mon Sep 17 00:00:00 2001
From: Vivin Krishna <123377523+vivinkrishna-ni@users.noreply.github.com>
Date: Tue, 12 Sep 2023 19:03:37 +0530
Subject: [PATCH 49/56] Fix test case fail in editor
---
.../src/rich-text/editor/tests/rich-text-editor.spec.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
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 09d242e917..b2e66d6cd2 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
@@ -653,7 +653,7 @@ describe('RichTextEditor', () => {
{ name: 'HTTP://NIMBLE.NI.DEV ' },
{ name: 'HttP://nimble.NI.dev ' },
{
- name: 'https://www.example.com/path/equals=ampersand&question?dollar$plus+comma,At@semicolon; '
+ name: 'https://www.example.com/path/=equals&ersand?question$dollar+plus,comma;semicolon@At '
},
{ name: 'https://example.com/my%20page.html ' },
{ name: 'https://example.com/smiley😀.html ' },
From 7169a06142fcfe77ce51596b6c2818ccbe257f84 Mon Sep 17 00:00:00 2001
From: Vivin Krishna <123377523+vivinkrishna-ni@users.noreply.github.com>
Date: Wed, 13 Sep 2023 10:27:09 +0530
Subject: [PATCH 50/56] Update the code comment to explain the rationale for
the same.
---
.../rich-text/editor/tests/rich-text-editor.spec.ts | 1 +
.../src/rich-text/models/markdown-serializer.ts | 13 +++++++++++--
2 files changed, 12 insertions(+), 2 deletions(-)
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 b2e66d6cd2..9babcc1010 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
@@ -679,6 +679,7 @@ describe('RichTextEditor', () => {
'A'
]);
expect(pageObject.getEditorLeafContents()).toEqual([
+ // Name without the trailing space used by the editor to trigger conversion to a link
value.name.slice(0, -1)
]);
}
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 f48e4ebeb9..198c9cd6f8 100644
--- a/packages/nimble-components/src/rich-text/models/markdown-serializer.ts
+++ b/packages/nimble-components/src/rich-text/models/markdown-serializer.ts
@@ -52,8 +52,17 @@ export class RichTextMarkdownSerializer {
const marks = {
italic: defaultMarkdownSerializer.marks.em!,
bold: defaultMarkdownSerializer.marks.strong!,
- // Autolink markdown in CommonMark flavor: https://spec.commonmark.org/0.30/#autolinks
- // ProseMirror model reference: https://github.com/ProseMirror/prosemirror-markdown/blob/c7210d0e55c82bfb0b2f7cba5dffe804575fafb3/src/to_markdown.ts#L3C1-L26C2
+ /**
+ * When a user inserts an absolute link into the editor and then modifies it, the 'defaultMarkdownSerializer.marks.link' function
+ * will detect whether it should be serialized as an autolink () or a regular link ([text](url)) in Markdown format by
+ * comparing the link text with 'href'. Since our markdown-parser only supports the autolink format, we need to ensure that the
+ * serializer also only supports autolink. Unfortunately, prosemirror-markdown does not offer a built-in way to update the
+ * 'defaultMarkdownSerializer' for this purpose. Therefore, we had to create a modified implementation to enable support for
+ * only autolink in serialization.
+
+ * Autolink markdown in CommonMark flavor: https://spec.commonmark.org/0.30/#autolinks
+ * ProseMirror model reference: https://github.com/ProseMirror/prosemirror-markdown/blob/c7210d0e55c82bfb0b2f7cba5dffe804575fafb3/src/to_markdown.ts#L3C1-L26C2
+ */
link: {
open: '<',
close: '>',
From 8de836102f52de75111ba149dd77611578324896 Mon Sep 17 00:00:00 2001
From: Vivin Krishna <123377523+vivinkrishna-ni@users.noreply.github.com>
Date: Thu, 14 Sep 2023 16:40:26 +0530
Subject: [PATCH 51/56] Resolve PR comments
---
.../src/rich-text/editor/index.ts | 9 ++++++
.../src/rich-text/editor/styles.ts | 11 +++++++
.../editor/tests/rich-text-editor.spec.ts | 3 +-
.../src/rich-text/models/markdown-parser.ts | 15 +++++++--
.../rich-text/models/markdown-serializer.ts | 24 ++++++++------
.../models/tests/markdown-parser.spec.ts | 5 +++
.../models/tests/markdown-serializer.spec.ts | 31 +++++++------------
7 files changed, 65 insertions(+), 33 deletions(-)
diff --git a/packages/nimble-components/src/rich-text/editor/index.ts b/packages/nimble-components/src/rich-text/editor/index.ts
index 0d9d5e71ba..88f394e722 100644
--- a/packages/nimble-components/src/rich-text/editor/index.ts
+++ b/packages/nimble-components/src/rich-text/editor/index.ts
@@ -365,6 +365,8 @@ export class RichTextEditor extends FoundationElement implements ErrorPattern {
},
autolink: true,
openOnClick: false,
+ // linkOnPaste can be enabled when hyperlink support added
+ // See: https://github.com/ni/nimble/issues/1527
linkOnPaste: false,
validate: href => /^https?:\/\//i.test(href)
})
@@ -382,9 +384,16 @@ export class RichTextEditor extends FoundationElement implements ErrorPattern {
*/
private getCustomLinkExtension(): Mark {
return Link.extend({
+ // Excludes can be removed enabled when hyperlink support added
+ // See: https://github.com/ni/nimble/issues/1527
excludes: '_',
+ // Inclusive can be updated when hyperlink support added
+ // See: https://github.com/ni/nimble/issues/1527
inclusive: false,
parseHTML() {
+ // As the markdown parser parses the links in the markdown string to 'nimble-anchor' tags,
+ // the link extension should identify that all 'nimble-anchor' tags are links. Therefore, it should
+ // return the 'nimble-anchor' tag.
return [{ tag: anchorTag }];
},
// HTMLAttribute cannot be in camelCase as we want to match it with the name in Tiptap
diff --git a/packages/nimble-components/src/rich-text/editor/styles.ts b/packages/nimble-components/src/rich-text/editor/styles.ts
index 902207e71e..4d8bc3789e 100644
--- a/packages/nimble-components/src/rich-text/editor/styles.ts
+++ b/packages/nimble-components/src/rich-text/editor/styles.ts
@@ -189,6 +189,12 @@ export const styles = css`
color: ${controlLabelDisabledFontColor};
}
+ ${
+ /**
+ * Custom anchor stylings can be removed once leveraging 'nimble-anchor' is supported.
+ * See: https://github.com/ni/nimble/issues/1516
+ */ ''
+ }
.ProseMirror a {
color: ${linkFontColor};
white-space: normal;
@@ -198,6 +204,9 @@ export const styles = css`
* the link's text content, the 'href' attribute of the anchor tag will not be updated. If they attempt to open it using
* the right-click context menu with 'Open in new tab/window,' it will still navigate to the link specified
* in the 'href' attribute, which may create unnecessary confusion while trying to open the link.
+ *
+ * Using pointer-events: none to disable link interactions can be removed when hyperlink support is added.
+ * see: https://github.com/ni/nimble/issues/1527
*/ ''
}
pointer-events: none;
@@ -209,6 +218,8 @@ export const styles = css`
cursor: default;
}
+ ${/** End of anchor styles */ ''}
+
.footer-section {
display: flex;
justify-content: space-between;
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 f06162007b..9b55942546 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
@@ -679,6 +679,7 @@ describe('RichTextEditor', () => {
},
{ name: 'https://example.com/my%20page.html ' },
{ name: 'https://example.com/smiley😀.html ' },
+ { name: 'https://www.😀.com ' },
{ name: 'https://example.com/пример.html ' }
];
@@ -709,7 +710,7 @@ describe('RichTextEditor', () => {
}
});
- it('should have the right attributes to the "a" tag', async () => {
+ it('the "a" tag should have href and rel attributes', async () => {
await pageObject.setEditorTextContent(
'https://nimble.ni.dev/ '
);
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 446d03496a..392fc607e4 100644
--- a/packages/nimble-components/src/rich-text/models/markdown-parser.ts
+++ b/packages/nimble-components/src/rich-text/models/markdown-parser.ts
@@ -54,9 +54,14 @@ export class RichTextMarkdownParser {
supportedTokenizerRules.validateLink = href => /^https?:\/\//i.test(href);
- // In order to display encoded characters, non-ASCII characters, emojis, and other special characters in their original form,
- // we bypass the default normalization of link text in markdown-it. This is done because we support only "AutoLink" feature in CommonMark flavor.
- // "normalizeLinkText" method reference in markdown-it: https://github.com/markdown-it/markdown-it/blob/2b6cac25823af011ff3bc7628bc9b06e483c5a08/lib/index.js#L67C1-L86C2
+ /**
+ * In order to display encoded characters, non-ASCII characters, emojis, and other special characters in their original form,
+ * we bypass the default normalization of link text in markdown-it. This is done because we support only "AutoLink" feature in CommonMark flavor.
+ * "normalizeLinkText" method reference in markdown-it: https://github.com/markdown-it/markdown-it/blob/2b6cac25823af011ff3bc7628bc9b06e483c5a08/lib/index.js#L67C1-L86C2
+ *
+ * We can use the default normalization once hyperlink support is added.
+ * See: https://github.com/ni/nimble/issues/1527
+ */
supportedTokenizerRules.normalizeLinkText = url => url;
return new MarkdownParser(
@@ -75,7 +80,11 @@ export class RichTextMarkdownParser {
href: {},
rel: { default: 'noopener noreferrer' }
},
+ // Inclusive can be updated when hyperlink support added
+ // See: https://github.com/ni/nimble/issues/1527
inclusive: false,
+ // Excludes can be removed enabled when hyperlink support added
+ // See: https://github.com/ni/nimble/issues/1527
excludes: '_',
toDOM(node) {
return [
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 198c9cd6f8..b6e8a145ef 100644
--- a/packages/nimble-components/src/rich-text/models/markdown-serializer.ts
+++ b/packages/nimble-components/src/rich-text/models/markdown-serializer.ts
@@ -53,16 +53,20 @@ export class RichTextMarkdownSerializer {
italic: defaultMarkdownSerializer.marks.em!,
bold: defaultMarkdownSerializer.marks.strong!,
/**
- * When a user inserts an absolute link into the editor and then modifies it, the 'defaultMarkdownSerializer.marks.link' function
- * will detect whether it should be serialized as an autolink () or a regular link ([text](url)) in Markdown format by
- * comparing the link text with 'href'. Since our markdown-parser only supports the autolink format, we need to ensure that the
- * serializer also only supports autolink. Unfortunately, prosemirror-markdown does not offer a built-in way to update the
- * 'defaultMarkdownSerializer' for this purpose. Therefore, we had to create a modified implementation to enable support for
- * only autolink in serialization.
-
- * Autolink markdown in CommonMark flavor: https://spec.commonmark.org/0.30/#autolinks
- * ProseMirror model reference: https://github.com/ProseMirror/prosemirror-markdown/blob/c7210d0e55c82bfb0b2f7cba5dffe804575fafb3/src/to_markdown.ts#L3C1-L26C2
- */
+ * When a user inserts an absolute link into the editor and then modifies it, the 'defaultMarkdownSerializer.marks.link' function
+ * will detect whether it should be serialized as an autolink () or a hyperlink ([text](url)) in Markdown format by
+ * comparing the link text with 'href'. Since our markdown-parser only supports the autolink format, we need to ensure that the
+ * serializer also only supports autolink. Unfortunately, prosemirror-markdown does not offer a built-in way to update the
+ * 'defaultMarkdownSerializer' for this purpose. Therefore, we had to create a modified implementation to enable support for
+ * only autolink in serialization. This modified implementation will just load the link text content in between '<>' angular brackets
+ * and ignores the 'href' part.
+ *
+ * Autolink markdown in CommonMark flavor: https://spec.commonmark.org/0.30/#autolinks
+ * ProseMirror model reference: https://github.com/ProseMirror/prosemirror-markdown/blob/c7210d0e55c82bfb0b2f7cba5dffe804575fafb3/src/to_markdown.ts#L3C1-L26C2
+ *
+ * The defaultMarkdownSerializer can be used once hyperlink support is added:
+ * See: https://github.com/ni/nimble/issues/1527
+ */
link: {
open: '<',
close: '>',
diff --git a/packages/nimble-components/src/rich-text/models/tests/markdown-parser.spec.ts b/packages/nimble-components/src/rich-text/models/tests/markdown-parser.spec.ts
index a1ad019e66..890a891402 100644
--- a/packages/nimble-components/src/rich-text/models/tests/markdown-parser.spec.ts
+++ b/packages/nimble-components/src/rich-text/models/tests/markdown-parser.spec.ts
@@ -327,6 +327,11 @@ describe('Markdown parser', () => {
validLink: '',
encodeURL: 'https://example.com/smiley%F0%9F%98%80.html'
},
+ {
+ name: 'Emoji at the host (punycode encoded)',
+ validLink: '',
+ encodeURL: 'https://www.xn--h28h.com'
+ },
{
name: 'Square brackets',
validLink: '',
diff --git a/packages/nimble-components/src/rich-text/models/tests/markdown-serializer.spec.ts b/packages/nimble-components/src/rich-text/models/tests/markdown-serializer.spec.ts
index 9dffc7a853..68c6e1c2c8 100644
--- a/packages/nimble-components/src/rich-text/models/tests/markdown-serializer.spec.ts
+++ b/packages/nimble-components/src/rich-text/models/tests/markdown-serializer.spec.ts
@@ -56,11 +56,9 @@ describe('Markdown serializer', () => {
markdown: '*Italics*'
},
{
- // All links will have `` tag here as an input, since it is a mock editor.
- // In the actual editor, links will render as `nimble-anchor` using `renderHTML`.
name: 'Link',
- html: 'Link
',
- markdown: ' '
+ html: 'https://nimble.ni.dev
',
+ markdown: ''
},
{
name: 'Bold and Italics',
@@ -69,18 +67,18 @@ describe('Markdown serializer', () => {
},
{
name: 'Link and Bold',
- html: 'Link and Bold
',
- markdown: ' '
+ html: 'https://nimble.ni.dev
',
+ markdown: ''
},
{
name: 'Link and Italics',
- html: 'Link and Italics
',
- markdown: ' '
+ html: 'https://nimble.ni.dev
',
+ markdown: ''
},
{
name: 'Link, Bold and Italics',
- html: 'Link, Bold and Italics
',
- markdown: ' '
+ html: 'https://nimble.ni.dev
',
+ markdown: ''
},
{
name: 'Numbered list',
@@ -104,8 +102,8 @@ describe('Markdown serializer', () => {
},
{
name: 'Numbered list with link',
- html: 'Numbered list with link
',
- markdown: '1. '
+ html: 'https://nimble.ni.dev
',
+ markdown: '1. '
},
{
name: 'Bulleted list',
@@ -127,15 +125,10 @@ describe('Markdown serializer', () => {
html: '',
markdown: '* *Bulleted list with italics*'
},
- {
- name: 'Bullet list with italics',
- html: '',
- markdown: '* *Bullet list with italics*'
- },
{
name: 'Bullet list with link',
- html: '',
- markdown: '* '
+ html: '',
+ markdown: '* '
},
{
name: 'Nested list with levels 1 - Bulleted list, 2 - Numbered list (Bold)',
From 3a35a26e6577dc3f57d6862e14a506991dc60329 Mon Sep 17 00:00:00 2001
From: Vivin Krishna <123377523+vivinkrishna-ni@users.noreply.github.com>
Date: Thu, 14 Sep 2023 16:55:53 +0530
Subject: [PATCH 52/56] Minor comment error change
---
packages/nimble-components/src/rich-text/editor/index.ts | 2 +-
.../nimble-components/src/rich-text/models/markdown-parser.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 88f394e722..287e146ffa 100644
--- a/packages/nimble-components/src/rich-text/editor/index.ts
+++ b/packages/nimble-components/src/rich-text/editor/index.ts
@@ -384,7 +384,7 @@ export class RichTextEditor extends FoundationElement implements ErrorPattern {
*/
private getCustomLinkExtension(): Mark {
return Link.extend({
- // Excludes can be removed enabled when hyperlink support added
+ // Excludes can be removed/enabled when hyperlink support added
// See: https://github.com/ni/nimble/issues/1527
excludes: '_',
// Inclusive can be updated when hyperlink support added
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 392fc607e4..ce7b2aaea4 100644
--- a/packages/nimble-components/src/rich-text/models/markdown-parser.ts
+++ b/packages/nimble-components/src/rich-text/models/markdown-parser.ts
@@ -83,7 +83,7 @@ export class RichTextMarkdownParser {
// Inclusive can be updated when hyperlink support added
// See: https://github.com/ni/nimble/issues/1527
inclusive: false,
- // Excludes can be removed enabled when hyperlink support added
+ // Excludes can be removed/enabled when hyperlink support added
// See: https://github.com/ni/nimble/issues/1527
excludes: '_',
toDOM(node) {
From 9b0478772c9ea569e3fac083f5dc01791de71392 Mon Sep 17 00:00:00 2001
From: Vivin Krishna <123377523+vivinkrishna-ni@users.noreply.github.com>
Date: Thu, 14 Sep 2023 16:59:34 +0530
Subject: [PATCH 53/56] Minor test case update
---
.../src/rich-text/models/tests/markdown-parser.spec.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/nimble-components/src/rich-text/models/tests/markdown-parser.spec.ts b/packages/nimble-components/src/rich-text/models/tests/markdown-parser.spec.ts
index 890a891402..dbef096d96 100644
--- a/packages/nimble-components/src/rich-text/models/tests/markdown-parser.spec.ts
+++ b/packages/nimble-components/src/rich-text/models/tests/markdown-parser.spec.ts
@@ -330,7 +330,7 @@ describe('Markdown parser', () => {
{
name: 'Emoji at the host (punycode encoded)',
validLink: '',
- encodeURL: 'https://www.xn--h28h.com'
+ encodeURL: 'https://www.xn--e28h.com/'
},
{
name: 'Square brackets',
From f425b5c150bf2ba240c810754e7201822ca5b4c2 Mon Sep 17 00:00:00 2001
From: Vivin Krishna <123377523+vivinkrishna-ni@users.noreply.github.com>
Date: Thu, 14 Sep 2023 17:17:02 +0530
Subject: [PATCH 54/56] Removed forward slash in emoji test case
---
.../src/rich-text/models/tests/markdown-parser.spec.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/nimble-components/src/rich-text/models/tests/markdown-parser.spec.ts b/packages/nimble-components/src/rich-text/models/tests/markdown-parser.spec.ts
index dbef096d96..c84da64367 100644
--- a/packages/nimble-components/src/rich-text/models/tests/markdown-parser.spec.ts
+++ b/packages/nimble-components/src/rich-text/models/tests/markdown-parser.spec.ts
@@ -330,7 +330,7 @@ describe('Markdown parser', () => {
{
name: 'Emoji at the host (punycode encoded)',
validLink: '',
- encodeURL: 'https://www.xn--e28h.com/'
+ encodeURL: 'https://www.xn--e28h.com'
},
{
name: 'Square brackets',
From 533509119d93651ce559915f088f6583a6b29a65 Mon Sep 17 00:00:00 2001
From: Vivin Krishna <123377523+vivinkrishna-ni@users.noreply.github.com>
Date: Thu, 14 Sep 2023 20:05:56 +0530
Subject: [PATCH 55/56] Added matrix test for long link in mobile width
---
.../editor/tests/rich-text-editor-matrix.stories.ts | 9 +++++++++
1 file changed, 9 insertions(+)
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
index 5e95d60d5b..bfe669f555 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
@@ -197,6 +197,15 @@ longWordContentInMobileWidth.play = (): void => {
);
};
+export const longLinkInMobileWidth: StoryFn = createStory(mobileWidthComponent);
+longLinkInMobileWidth.play = (): void => {
+ document
+ .querySelector('nimble-rich-text-editor')!
+ .setMarkdown(
+ ''
+ );
+};
+
export const hiddenRichTextEditor: StoryFn = createStory(
hiddenWrapper(html`<${richTextEditorTag} hidden>${richTextEditorTag}>`)
);
From 23b8fddb6422ea0fdd33ad7c7a1a27f9ae2be794 Mon Sep 17 00:00:00 2001
From: Vivin Krishna <123377523+vivinkrishna-ni@users.noreply.github.com>
Date: Thu, 14 Sep 2023 20:20:03 +0530
Subject: [PATCH 56/56] Updated the comment of parseHTML in editor
---
packages/nimble-components/src/rich-text/editor/index.ts | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/packages/nimble-components/src/rich-text/editor/index.ts b/packages/nimble-components/src/rich-text/editor/index.ts
index 287e146ffa..c9b5337666 100644
--- a/packages/nimble-components/src/rich-text/editor/index.ts
+++ b/packages/nimble-components/src/rich-text/editor/index.ts
@@ -391,9 +391,9 @@ export class RichTextEditor extends FoundationElement implements ErrorPattern {
// See: https://github.com/ni/nimble/issues/1527
inclusive: false,
parseHTML() {
- // As the markdown parser parses the links in the markdown string to 'nimble-anchor' tags,
- // the link extension should identify that all 'nimble-anchor' tags are links. Therefore, it should
- // return the 'nimble-anchor' tag.
+ // To load the `nimble-anchor` from the HTML parsed content by markdown-parser as links in the
+ // Tiptap editor, the `parseHTML` of Link extension should return `anchorTag`. This is because the
+ // link mark schema in `markdown-parser.ts` file uses `` as anchor tag and not ``.
return [{ tag: anchorTag }];
},
// HTMLAttribute cannot be in camelCase as we want to match it with the name in Tiptap