diff --git a/apps/map-viewer/project.json b/apps/map-viewer/project.json index 0ccb1c8bef..a7fade28af 100644 --- a/apps/map-viewer/project.json +++ b/apps/map-viewer/project.json @@ -42,7 +42,7 @@ { "type": "anyComponentStyle", "maximumWarning": "2kb", - "maximumError": "4kb" + "maximumError": "5kb" } ], "fileReplacements": [ diff --git a/apps/metadata-editor/project.json b/apps/metadata-editor/project.json index e93f92e72b..e38fcc319a 100644 --- a/apps/metadata-editor/project.json +++ b/apps/metadata-editor/project.json @@ -52,7 +52,7 @@ { "type": "anyComponentStyle", "maximumWarning": "2kb", - "maximumError": "4kb" + "maximumError": "5kb" } ], "outputHashing": "all" diff --git a/apps/search/project.json b/apps/search/project.json index 0fdf04538b..ff98cad88b 100644 --- a/apps/search/project.json +++ b/apps/search/project.json @@ -33,7 +33,7 @@ { "type": "anyComponentStyle", "maximumWarning": "2kb", - "maximumError": "4kb" + "maximumError": "5kb" } ], "fileReplacements": [ diff --git a/libs/ui/elements/src/index.ts b/libs/ui/elements/src/index.ts index 61cb50dd29..ed3b7f3ad5 100644 --- a/libs/ui/elements/src/index.ts +++ b/libs/ui/elements/src/index.ts @@ -19,3 +19,4 @@ export * from './lib/related-record-card/related-record-card.component' export * from './lib/search-results-error/search-results-error.component' export * from './lib/user-preview/user-preview.component' export * from './lib/record-api-form/record-api-form.component' +export * from './lib/markdown-parser/markdown-parser.component' diff --git a/libs/ui/elements/src/lib/markdown-parser/markdown-parser.component.css b/libs/ui/elements/src/lib/markdown-parser/markdown-parser.component.css new file mode 100644 index 0000000000..36dcdc95d6 --- /dev/null +++ b/libs/ui/elements/src/lib/markdown-parser/markdown-parser.component.css @@ -0,0 +1,264 @@ +/** Body **/ +:host /deep/ .markdown-body { + -ms-text-size-adjust: 100%; + -webkit-text-size-adjust: 100%; + margin: 0px 0px 1.5rem 0px; + line-height: 1.5; + word-wrap: break-word; +} + +/** Emphasis **/ + +:host /deep/ .markdown-body strong { + @apply font-bold; + color: var(--color-secondary-darker); +} + +/** Headings **/ + +:host /deep/ .markdown-body h1, +:host /deep/ .markdown-body h2, +:host /deep/ .markdown-body h3, +:host /deep/ .markdown-body h4, +:host /deep/ .markdown-body h5, +:host /deep/ .markdown-body h6 { + margin-top: 24px; + margin-bottom: 16px; + line-height: 1.25; + @apply text-title font-title font-bold; +} + +:host /deep/ .markdown-body h1 { + margin: 0.67em 0; + padding-bottom: 0.3em; + font-size: 2em; + color: var(--color-primary); +} + +:host /deep/ .markdown-body h2 { + padding-bottom: 0.3em; + font-size: 1.5em; + color: var(--color-secondary); +} + +:host /deep/ .markdown-body h3 { + font-size: 1.25em; + color: var(--color-secondary); +} + +:host /deep/ .markdown-body h4 { + font-size: 1em; + color: var(--color-secondary); +} + +:host /deep/ .markdown-body h5 { + font-size: 0.875em; + color: var(--color-secondary); +} + +:host /deep/ .markdown-body h6 { + font-size: 0.85em; + color: var(--color-secondary-lighter); +} + +/** Paragraphs **/ + +:host /deep/ .markdown-body p { + margin-top: 0; + margin-bottom: 10px; +} + +/** Links **/ + +:host /deep/ .markdown-body p > a { + margin-top: 0; + margin-bottom: 10px; + color: var(--color-primary) !important; + text-decoration: none !important; + @apply font-bold; +} + +:host /deep/ .markdown-body p > a:hover { + color: var(--color-primary-darker) !important; +} + +/** Blockquotes **/ + +:host /deep/ .markdown-body blockquote { + margin: 0; + padding: 0 1em; + color: var(--color-secondary-lighter); + border-left: 0.25em solid var(--color-primary-lighter); +} + +/** Code **/ + +:host /deep/ .markdown-body pre { + margin-top: 0; + margin-bottom: 0; + font-size: 12px; + background-color: var(--color-gray-100); + word-wrap: normal; +} + +:host /deep/ .markdown-body pre { + padding: 16px; + overflow: auto; + font-size: 85%; + line-height: 1.45; + color: var(--color-secondary); + border-radius: 6px; +} + +:host /deep/ .markdown-body code { + padding: 0.2em 0.4em; + margin: 0; + font-size: 85%; + white-space: break-spaces; + border-radius: 6px; +} + +:host /deep/ .markdown-body pre code { + display: inline; + max-width: auto; + padding: 0; + margin: 0; + overflow: visible; + line-height: inherit; + word-wrap: normal; + border: 0; +} + +/** Horizontal rules **/ + +:host /deep/ .markdown-body hr { + box-sizing: content-box; + overflow: hidden; + background: transparent; + border-bottom: 1px solid var(--color-secondary); + height: 0.15em; + padding: 0; + margin: 24px 0; + background-color: var(--color-secondary); + border: 0; +} + +:host /deep/ .markdown-body hr::before { + display: table; + content: ''; +} + +:host /deep/ .markdown-body hr::after { + display: table; + clear: both; + content: ''; +} + +/** Lists **/ + +:host /deep/ .markdown-body ul, +:host /deep/ .markdown-body ol { + margin-top: 0; + margin-bottom: 0; + padding-left: 2em; + list-style: revert; +} + +:host /deep/ .markdown-body ol ol, +:host /deep/ .markdown-body ul ol { + list-style-type: lower-roman; +} + +:host /deep/ .markdown-body ul ul ol, +:host /deep/ .markdown-body ul ol ol, +:host /deep/ .markdown-body ol ul ol, +:host /deep/ .markdown-body ol ol ol { + list-style-type: lower-alpha; +} + +:host /deep/ .markdown-body ol[type='a s'] { + list-style-type: lower-alpha; +} + +:host /deep/ .markdown-body ol[type='A s'] { + list-style-type: upper-alpha; +} + +:host /deep/ .markdown-body ol[type='i s'] { + list-style-type: lower-roman; +} + +:host /deep/ .markdown-body ol[type='I s'] { + list-style-type: upper-roman; +} + +:host /deep/ .markdown-body ol[type='1'] { + list-style: unset; + list-style-type: decimal; +} + +:host /deep/ .markdown-body div > ol:not([type]) { + list-style: unset; + list-style-type: decimal; +} + +/** Table **/ + +:host /deep/ .markdown-body table { + border-spacing: 0; + border-collapse: collapse; + display: block; + width: max-content; + max-width: 100%; + overflow: auto; + padding-bottom: 15px; +} + +:host /deep/ .markdown-body td, +:host /deep/ .markdown-body th { + padding: 0; +} + +:host /deep/ .markdown-body th { + color: var(--color-secondary); +} + +:host /deep/ .markdown-body table th, +:host /deep/ .markdown-body table td { + padding: 6px 13px; + border: 1px solid var(--color-gray-500); +} + +:host /deep/ .markdown-body table td > :last-child { + margin-bottom: 0; +} + +:host /deep/ .markdown-body table tr { + background-color: #ffffff; + border-top: 1px solid var(--color-secondary-lighter); +} + +:host /deep/ .markdown-body table tr:nth-child(2n) { + background-color: var(--color-gray-100); +} + +:host /deep/ .markdown-body table img { + background-color: transparent; +} + +/** Images **/ + +:host /deep/ .markdown-body img { + border-style: none; + max-width: 100%; + box-sizing: content-box; + background-color: transparent; +} + +:host /deep/ .markdown-body img[align='right'] { + padding-left: 20px; +} + +:host /deep/ .markdown-body img[align='left'] { + padding-right: 20px; +} diff --git a/libs/ui/elements/src/lib/markdown-parser/markdown-parser.component.html b/libs/ui/elements/src/lib/markdown-parser/markdown-parser.component.html new file mode 100644 index 0000000000..93f466a59a --- /dev/null +++ b/libs/ui/elements/src/lib/markdown-parser/markdown-parser.component.html @@ -0,0 +1 @@ +
diff --git a/libs/ui/elements/src/lib/markdown-parser/markdown-parser.component.spec.ts b/libs/ui/elements/src/lib/markdown-parser/markdown-parser.component.spec.ts new file mode 100644 index 0000000000..d8534a7bfc --- /dev/null +++ b/libs/ui/elements/src/lib/markdown-parser/markdown-parser.component.spec.ts @@ -0,0 +1,41 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing' +import { MarkdownParserComponent } from './markdown-parser.component' + +describe('MarkdownParserComponent', () => { + let component: MarkdownParserComponent + let fixture: ComponentFixturebold markdown
') + }) + + it('should render HTML as HTML', () => { + component.textContent = 'simple html
' + fixture.detectChanges() + const html = fixture.nativeElement.innerHTML + expect(html).toContain('simple html
') + }) + + it('should render text as HTML', () => { + component.textContent = 'simple text' + fixture.detectChanges() + const text = fixture.nativeElement.innerHTML + expect(text).toContain('simple text
') + }) +}) diff --git a/libs/ui/elements/src/lib/markdown-parser/markdown-parser.component.stories.ts b/libs/ui/elements/src/lib/markdown-parser/markdown-parser.component.stories.ts new file mode 100644 index 0000000000..b7664783e7 --- /dev/null +++ b/libs/ui/elements/src/lib/markdown-parser/markdown-parser.component.stories.ts @@ -0,0 +1,148 @@ +import { Meta, moduleMetadata, StoryObj } from '@storybook/angular' +import { MarkdownParserComponent } from './markdown-parser.component' + +export default { + title: 'Elements/MarkdownParserComponent', + component: MarkdownParserComponent, + decorators: [ + moduleMetadata({ + declarations: [MarkdownParserComponent], + }), + ], +} as Metarecord.metadata.keywords diff --git a/libs/ui/elements/src/lib/ui-elements.module.ts b/libs/ui/elements/src/lib/ui-elements.module.ts index 73b00327a2..eac4060e3b 100644 --- a/libs/ui/elements/src/lib/ui-elements.module.ts +++ b/libs/ui/elements/src/lib/ui-elements.module.ts @@ -29,6 +29,7 @@ import { GnUiLinkifyDirective } from './metadata-info/linkify.directive' import { PaginationButtonsComponent } from './pagination-buttons/pagination-buttons.component' import { MaxLinesComponent } from './max-lines/max-lines.component' import { RecordApiFormComponent } from './record-api-form/record-api-form.component' +import { MarkdownParserComponent } from './markdown-parser/markdown-parser.component' @NgModule({ imports: [ @@ -65,6 +66,7 @@ import { RecordApiFormComponent } from './record-api-form/record-api-form.compon PaginationButtonsComponent, MaxLinesComponent, RecordApiFormComponent, + MarkdownParserComponent, ], exports: [ MetadataInfoComponent, @@ -85,6 +87,7 @@ import { RecordApiFormComponent } from './record-api-form/record-api-form.compon UserPreviewComponent, PaginationButtonsComponent, RecordApiFormComponent, + MarkdownParserComponent, ], }) export class UiElementsModule {} diff --git a/package-lock.json b/package-lock.json index ff2f2588b7..e1d431a51d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -50,6 +50,7 @@ "embla-carousel": "^8.0.0-rc14", "express": "^4.17.1", "geojson-validation": "^1.0.2", + "marked": "^11.1.1", "moment": "^2.29.4", "ng-table-virtual-scroll": "^1.4.1", "ngx-chips": "3.0.0", @@ -23997,6 +23998,17 @@ "react": ">= 0.14.0" } }, + "node_modules/marked": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/marked/-/marked-11.1.1.tgz", + "integrity": "sha512-EgxRjgK9axsQuUa/oKMx5DEY8oXpKJfk61rT5iY3aRlgU6QJtUcxU5OAymdhCvWvhYcd9FKmO5eQoX8m9VGJXg==", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 18" + } + }, "node_modules/mdast-util-definitions": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-4.0.0.tgz", diff --git a/package.json b/package.json index d7589bf7cd..7c3bde2dd8 100644 --- a/package.json +++ b/package.json @@ -85,6 +85,7 @@ "embla-carousel": "^8.0.0-rc14", "express": "^4.17.1", "geojson-validation": "^1.0.2", + "marked": "^11.1.1", "moment": "^2.29.4", "ng-table-virtual-scroll": "^1.4.1", "ngx-chips": "3.0.0",