From bd6dca0b5def17952197a493be7ae14d9654a601 Mon Sep 17 00:00:00 2001 From: alexcibotari Date: Fri, 29 Nov 2024 22:56:37 +0100 Subject: [PATCH] Add Rich Text Schema --- functions/src/models/schema.model.ts | 8 + functions/src/models/schema.zod.ts | 7 + package-lock.json | 580 +++++++++++++++++- package.json | 9 + .../spaces/contents/contents.module.ts | 17 +- .../edit-document-schema.component.html | 6 + .../rich-text-editor.component.html | 67 ++ .../rich-text-editor.component.scss | 4 + .../rich-text-editor.component.ts | 48 ++ .../edit-field/edit-field.component.html | 5 +- .../shared/edit-field/edit-field.component.ts | 27 +- src/app/shared/models/schema.model.ts | 15 +- .../shared/services/content-helper.service.ts | 1 + src/styles.scss | 1 + src/styles/_rich-text-editor.scss | 7 + 15 files changed, 777 insertions(+), 25 deletions(-) create mode 100644 src/app/features/spaces/contents/shared/rich-text-editor/rich-text-editor.component.html create mode 100644 src/app/features/spaces/contents/shared/rich-text-editor/rich-text-editor.component.scss create mode 100644 src/app/features/spaces/contents/shared/rich-text-editor/rich-text-editor.component.ts create mode 100644 src/styles/_rich-text-editor.scss diff --git a/functions/src/models/schema.model.ts b/functions/src/models/schema.model.ts index 2bcb3bfa..aeadce02 100644 --- a/functions/src/models/schema.model.ts +++ b/functions/src/models/schema.model.ts @@ -37,6 +37,7 @@ export interface SchemaEnumValue { export type SchemaField = | SchemaFieldText | SchemaFieldTextarea + | SchemaFieldRichText | SchemaFieldMarkdown | SchemaFieldNumber | SchemaFieldColor @@ -56,6 +57,7 @@ export type SchemaField = export enum SchemaFieldKind { TEXT = 'TEXT', TEXTAREA = 'TEXTAREA', + RICH_TEXT = 'RICH_TEXT', MARKDOWN = 'MARKDOWN', NUMBER = 'NUMBER', COLOR = 'COLOR', @@ -95,6 +97,12 @@ export interface SchemaFieldTextarea extends SchemaFieldBase { maxLength?: number; } +export interface SchemaFieldRichText extends SchemaFieldBase { + kind: SchemaFieldKind.RICH_TEXT; + minLength?: number; + maxLength?: number; +} + export interface SchemaFieldMarkdown extends SchemaFieldBase { kind: SchemaFieldKind.MARKDOWN; minLength?: number; diff --git a/functions/src/models/schema.zod.ts b/functions/src/models/schema.zod.ts index a3fe8942..0ba2e3ca 100644 --- a/functions/src/models/schema.zod.ts +++ b/functions/src/models/schema.zod.ts @@ -43,6 +43,12 @@ export const schemaFieldTextareaSchema = schemaFieldBaseSchema.extend({ maxLength: z.number().optional(), }); +export const schemaFieldRichTextSchema = schemaFieldBaseSchema.extend({ + kind: z.literal(SchemaFieldKind.RICH_TEXT), + minLength: z.number().optional(), + maxLength: z.number().optional(), +}); + export const schemaFieldMarkdownSchema = schemaFieldBaseSchema.extend({ kind: z.literal(SchemaFieldKind.MARKDOWN), minLength: z.number().optional(), @@ -134,6 +140,7 @@ export const schemaFieldAssetsSchema = schemaFieldBaseSchema.extend({ export const schemaFieldSchema = z.union([ schemaFieldTextSchema, schemaFieldTextareaSchema, + schemaFieldRichTextSchema, schemaFieldMarkdownSchema, schemaFieldNumberSchema, schemaFieldColorSchema, diff --git a/package-lock.json b/package-lock.json index d5cdefa1..befb3548 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,10 +24,19 @@ "@ngrx/signals": "^18.1.0", "@ngrx/store-devtools": "^18.1.0", "@stoplight/elements": "^8.4.6", + "@tiptap/extension-bold": "^2.7.1", + "@tiptap/extension-document": "^2.7.1", + "@tiptap/extension-italic": "^2.7.1", + "@tiptap/extension-paragraph": "^2.7.1", + "@tiptap/extension-placeholder": "^2.7.1", + "@tiptap/extension-strike": "^2.7.1", + "@tiptap/extension-text": "^2.7.1", + "@tiptap/extension-underline": "^2.7.1", "browser-detect": "^0.2.28", "file-saver-es": "^2.0.5", "marked": "^12.0.2", "ngx-markdown": "^18.0.0", + "ngx-tiptap": "^11.1.0", "prismjs": "^1.29.0", "rxjs": "^7.4.0", "tslib": "^2.3.0", @@ -5350,6 +5359,17 @@ "integrity": "sha512-j7P6Rgr3mmtdkeDGTe0E/aYyWEWVtc5yFXtHCRHs28/jptDEWfaVOc5T7cblqy1XKPPfCxJc/8DwQ5YgLOZOVQ==", "dev": true }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "license": "MIT", + "peer": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, "node_modules/@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", @@ -5536,6 +5556,13 @@ "react": ">=16.8.0" } }, + "node_modules/@remirror/core-constants": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@remirror/core-constants/-/core-constants-3.0.0.tgz", + "integrity": "sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg==", + "license": "MIT", + "peer": true + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.20.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.20.0.tgz", @@ -6758,6 +6785,192 @@ "integrity": "sha512-Pb6M8TDO9DtSVla9yXSTAxmo9GVEouq5P40DWXdOie69bXogZTkgvopCq+yEvTMA0F6PEvdJmbtTV3ccIp11VQ==", "license": "Apache-2.0" }, + "node_modules/@tiptap/core": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-2.10.3.tgz", + "integrity": "sha512-wAG/0/UsLeZLmshWb6rtWNXKJftcmnned91/HLccHVQAuQZ1UWH+wXeQKu/mtodxEO7JcU2mVPR9mLGQkK0McQ==", + "license": "MIT", + "peer": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/pm": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-bold": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-bold/-/extension-bold-2.10.3.tgz", + "integrity": "sha512-xnF1tS2BsORenr11qyybW120gHaeHKiKq+ZOP14cGA0MsriKvWDnaCSocXP/xMEYHy7+2uUhJ0MsKkHVj4bPzQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-bubble-menu": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-bubble-menu/-/extension-bubble-menu-2.10.3.tgz", + "integrity": "sha512-e9a4yMjQezuKy0rtyyzxbV2IAE1bm1PY3yoZEFrcaY0o47g1CMUn2Hwe+9As2HdntEjQpWR7NO1mZeKxHlBPYA==", + "license": "MIT", + "peer": true, + "dependencies": { + "tippy.js": "^6.3.7" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0", + "@tiptap/pm": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-document": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-document/-/extension-document-2.10.3.tgz", + "integrity": "sha512-6i8+xbS2zB6t8iFzli1O/QB01MmwyI5Hqiiv4m5lOxqavmJwLss2sRhoMC2hB3CyFg5UmeODy/f/RnI6q5Vixg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-floating-menu": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-floating-menu/-/extension-floating-menu-2.10.3.tgz", + "integrity": "sha512-Prg8rYLxeyzHxfzVu1mDkkUWMnD9ZN3y370O/1qy55e+XKVw9jFkTSuz0y0+OhMJG6bulYpDUMtb+N3+2xOWlQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "tippy.js": "^6.3.7" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0", + "@tiptap/pm": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-italic": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-italic/-/extension-italic-2.10.3.tgz", + "integrity": "sha512-wAiO6ZxoHx2H90phnKttLWGPjPZXrfKxhOCsqYrK8BpRByhr48godOFRuGwYnKaiwoVjpxc63t+kDJDWvqmgMw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-paragraph": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-paragraph/-/extension-paragraph-2.10.3.tgz", + "integrity": "sha512-sNkTX/iN+YoleDiTJsrWSBw9D7c4vsYwnW5y/G5ydfuJMIRQMF78pWSIWZFDRNOMkgK5UHkhu9anrbCFYgBfaA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-placeholder": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-placeholder/-/extension-placeholder-2.10.3.tgz", + "integrity": "sha512-0OkwnDLguZgoiJM85cfnOySuMmPUF7qqw7DHQ+c3zwTAYnvzpvqrvpupc+2Zi9GfC1sDgr+Ajrp8imBHa6PHfA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0", + "@tiptap/pm": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-strike": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-strike/-/extension-strike-2.10.3.tgz", + "integrity": "sha512-jYoPy6F6njYp3txF3u23bgdRy/S5ATcWDO9LPZLHSeikwQfJ47nqb+EUNo5M8jIOgFBTn4MEbhuZ6OGyhnxopA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-text": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-text/-/extension-text-2.10.3.tgz", + "integrity": "sha512-7p9XiRprsRZm8y9jvF/sS929FCELJ5N9FQnbzikOiyGNUx5mdI+exVZlfvBr9xOD5s7fBLg6jj9Vs0fXPNRkPg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-underline": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-underline/-/extension-underline-2.10.3.tgz", + "integrity": "sha512-VeGs0jeNiTnXddHHJEgOc/sKljZiyTEgSSuqMmsBACrr9aGFXbLTgKTvNjkZ9WzSnu7LwgJuBrwEhg8yYixUyQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0" + } + }, + "node_modules/@tiptap/pm": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-2.10.3.tgz", + "integrity": "sha512-771p53aU0KFvujvKpngvq2uAxThlEsjYaXcVVmwrhf0vxSSg+psKQEvqvWvHv/3BwkPVCGwmEKNVJZjaXFKu4g==", + "license": "MIT", + "peer": true, + "dependencies": { + "prosemirror-changeset": "^2.2.1", + "prosemirror-collab": "^1.3.1", + "prosemirror-commands": "^1.6.2", + "prosemirror-dropcursor": "^1.8.1", + "prosemirror-gapcursor": "^1.3.2", + "prosemirror-history": "^1.4.1", + "prosemirror-inputrules": "^1.4.0", + "prosemirror-keymap": "^1.2.2", + "prosemirror-markdown": "^1.13.1", + "prosemirror-menu": "^1.2.4", + "prosemirror-model": "^1.23.0", + "prosemirror-schema-basic": "^1.2.3", + "prosemirror-schema-list": "^1.4.1", + "prosemirror-state": "^1.4.3", + "prosemirror-tables": "^1.6.1", + "prosemirror-trailing-node": "^3.0.0", + "prosemirror-transform": "^1.10.2", + "prosemirror-view": "^1.37.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + } + }, "node_modules/@tootallnate/quickjs-emscripten": { "version": "0.23.0", "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", @@ -6985,6 +7198,31 @@ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==" }, + "node_modules/@types/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==", + "license": "MIT", + "peer": true + }, + "node_modules/@types/markdown-it": { + "version": "14.1.2", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz", + "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/linkify-it": "^5", + "@types/mdurl": "^2" + } + }, + "node_modules/@types/markdown-it/node_modules/@types/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", + "license": "MIT", + "peer": true + }, "node_modules/@types/mdast": { "version": "3.0.15", "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.15.tgz", @@ -8012,7 +8250,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, "license": "Python-2.0" }, "node_modules/aria-query": { @@ -9417,6 +9654,13 @@ } } }, + "node_modules/crelt": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", + "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==", + "license": "MIT", + "peer": true + }, "node_modules/critters": { "version": "0.0.24", "resolved": "https://registry.npmjs.org/critters/-/critters-0.0.24.tgz", @@ -10688,7 +10932,6 @@ "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "devOptional": true, "engines": { "node": ">=0.12" }, @@ -14370,6 +14613,16 @@ "node": ">=10" } }, + "node_modules/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "uc.micro": "^2.0.0" + } + }, "node_modules/liquid-json": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/liquid-json/-/liquid-json-0.3.1.tgz", @@ -15086,6 +15339,31 @@ "node": "^16.14.0 || >=18.0.0" } }, + "node_modules/markdown-it": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", + "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", + "license": "MIT", + "peer": true, + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" + } + }, + "node_modules/markdown-it/node_modules/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", + "license": "MIT", + "peer": true + }, "node_modules/markdown-table": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-2.0.0.tgz", @@ -16804,6 +17082,30 @@ "zone.js": "~0.14.0" } }, + "node_modules/ngx-tiptap": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/ngx-tiptap/-/ngx-tiptap-11.1.0.tgz", + "integrity": "sha512-CM7oWC9BrA9/ZBbNdU72uyzfQ+hQXZU/fdGCW2ODFkMD3RQnbydzpibeHwRsFvObsnwCBoI3QVRpACedAs/8zw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/common": ">=18.0.0", + "@angular/core": ">=18.0.0", + "@angular/forms": ">=18.0.0", + "@tiptap/core": ">=2.7.0", + "@tiptap/extension-bubble-menu": "^2.7.0", + "@tiptap/extension-floating-menu": "^2.7.0", + "@tiptap/pm": "^2.7.0" + } + }, "node_modules/nice-napi": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/nice-napi/-/nice-napi-1.0.2.tgz", @@ -17442,6 +17744,13 @@ "dev": true, "license": "MIT" }, + "node_modules/orderedmap": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/orderedmap/-/orderedmap-2.1.1.tgz", + "integrity": "sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==", + "license": "MIT", + "peer": true + }, "node_modules/os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", @@ -18555,6 +18864,232 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/prosemirror-changeset": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/prosemirror-changeset/-/prosemirror-changeset-2.2.1.tgz", + "integrity": "sha512-J7msc6wbxB4ekDFj+n9gTW/jav/p53kdlivvuppHsrZXCaQdVgRghoZbSS3kwrRyAstRVQ4/+u5k7YfLgkkQvQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "prosemirror-transform": "^1.0.0" + } + }, + "node_modules/prosemirror-collab": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/prosemirror-collab/-/prosemirror-collab-1.3.1.tgz", + "integrity": "sha512-4SnynYR9TTYaQVXd/ieUvsVV4PDMBzrq2xPUWutHivDuOshZXqQ5rGbZM84HEaXKbLdItse7weMGOUdDVcLKEQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "prosemirror-state": "^1.0.0" + } + }, + "node_modules/prosemirror-commands": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.6.2.tgz", + "integrity": "sha512-0nDHH++qcf/BuPLYvmqZTUUsPJUCPBUXt0J1ErTcDIS369CTp773itzLGIgIXG4LJXOlwYCr44+Mh4ii6MP1QA==", + "license": "MIT", + "peer": true, + "dependencies": { + "prosemirror-model": "^1.0.0", + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.10.2" + } + }, + "node_modules/prosemirror-dropcursor": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/prosemirror-dropcursor/-/prosemirror-dropcursor-1.8.1.tgz", + "integrity": "sha512-M30WJdJZLyXHi3N8vxN6Zh5O8ZBbQCz0gURTfPmTIBNQ5pxrdU7A58QkNqfa98YEjSAL1HUyyU34f6Pm5xBSGw==", + "license": "MIT", + "peer": true, + "dependencies": { + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.1.0", + "prosemirror-view": "^1.1.0" + } + }, + "node_modules/prosemirror-gapcursor": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/prosemirror-gapcursor/-/prosemirror-gapcursor-1.3.2.tgz", + "integrity": "sha512-wtjswVBd2vaQRrnYZaBCbyDqr232Ed4p2QPtRIUK5FuqHYKGWkEwl08oQM4Tw7DOR0FsasARV5uJFvMZWxdNxQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "prosemirror-keymap": "^1.0.0", + "prosemirror-model": "^1.0.0", + "prosemirror-state": "^1.0.0", + "prosemirror-view": "^1.0.0" + } + }, + "node_modules/prosemirror-history": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/prosemirror-history/-/prosemirror-history-1.4.1.tgz", + "integrity": "sha512-2JZD8z2JviJrboD9cPuX/Sv/1ChFng+xh2tChQ2X4bB2HeK+rra/bmJ3xGntCcjhOqIzSDG6Id7e8RJ9QPXLEQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "prosemirror-state": "^1.2.2", + "prosemirror-transform": "^1.0.0", + "prosemirror-view": "^1.31.0", + "rope-sequence": "^1.3.0" + } + }, + "node_modules/prosemirror-inputrules": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/prosemirror-inputrules/-/prosemirror-inputrules-1.4.0.tgz", + "integrity": "sha512-6ygpPRuTJ2lcOXs9JkefieMst63wVJBgHZGl5QOytN7oSZs3Co/BYbc3Yx9zm9H37Bxw8kVzCnDsihsVsL4yEg==", + "license": "MIT", + "peer": true, + "dependencies": { + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.0.0" + } + }, + "node_modules/prosemirror-keymap": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/prosemirror-keymap/-/prosemirror-keymap-1.2.2.tgz", + "integrity": "sha512-EAlXoksqC6Vbocqc0GtzCruZEzYgrn+iiGnNjsJsH4mrnIGex4qbLdWWNza3AW5W36ZRrlBID0eM6bdKH4OStQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "prosemirror-state": "^1.0.0", + "w3c-keyname": "^2.2.0" + } + }, + "node_modules/prosemirror-markdown": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/prosemirror-markdown/-/prosemirror-markdown-1.13.1.tgz", + "integrity": "sha512-Sl+oMfMtAjWtlcZoj/5L/Q39MpEnVZ840Xo330WJWUvgyhNmLBLN7MsHn07s53nG/KImevWHSE6fEj4q/GihHw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/markdown-it": "^14.0.0", + "markdown-it": "^14.0.0", + "prosemirror-model": "^1.20.0" + } + }, + "node_modules/prosemirror-menu": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/prosemirror-menu/-/prosemirror-menu-1.2.4.tgz", + "integrity": "sha512-S/bXlc0ODQup6aiBbWVsX/eM+xJgCTAfMq/nLqaO5ID/am4wS0tTCIkzwytmao7ypEtjj39i7YbJjAgO20mIqA==", + "license": "MIT", + "peer": true, + "dependencies": { + "crelt": "^1.0.0", + "prosemirror-commands": "^1.0.0", + "prosemirror-history": "^1.0.0", + "prosemirror-state": "^1.0.0" + } + }, + "node_modules/prosemirror-model": { + "version": "1.24.0", + "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.24.0.tgz", + "integrity": "sha512-Ft7epNnycoQSM+2ObF35SBbBX+5WY39v8amVlrtlAcpglhlHs2tCTnWl7RX5tbp/PsMKcRcWV9cXPuoBWq0AIQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "orderedmap": "^2.0.0" + } + }, + "node_modules/prosemirror-schema-basic": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/prosemirror-schema-basic/-/prosemirror-schema-basic-1.2.3.tgz", + "integrity": "sha512-h+H0OQwZVqMon1PNn0AG9cTfx513zgIG2DY00eJ00Yvgb3UD+GQ/VlWW5rcaxacpCGT1Yx8nuhwXk4+QbXUfJA==", + "license": "MIT", + "peer": true, + "dependencies": { + "prosemirror-model": "^1.19.0" + } + }, + "node_modules/prosemirror-schema-list": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/prosemirror-schema-list/-/prosemirror-schema-list-1.4.1.tgz", + "integrity": "sha512-jbDyaP/6AFfDfu70VzySsD75Om2t3sXTOdl5+31Wlxlg62td1haUpty/ybajSfJ1pkGadlOfwQq9kgW5IMo1Rg==", + "license": "MIT", + "peer": true, + "dependencies": { + "prosemirror-model": "^1.0.0", + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.7.3" + } + }, + "node_modules/prosemirror-state": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/prosemirror-state/-/prosemirror-state-1.4.3.tgz", + "integrity": "sha512-goFKORVbvPuAQaXhpbemJFRKJ2aixr+AZMGiquiqKxaucC6hlpHNZHWgz5R7dS4roHiwq9vDctE//CZ++o0W1Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "prosemirror-model": "^1.0.0", + "prosemirror-transform": "^1.0.0", + "prosemirror-view": "^1.27.0" + } + }, + "node_modules/prosemirror-tables": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/prosemirror-tables/-/prosemirror-tables-1.6.1.tgz", + "integrity": "sha512-p8WRJNA96jaNQjhJolmbxTzd6M4huRE5xQ8OxjvMhQUP0Nzpo4zz6TztEiwk6aoqGBhz9lxRWR1yRZLlpQN98w==", + "license": "MIT", + "peer": true, + "dependencies": { + "prosemirror-keymap": "^1.1.2", + "prosemirror-model": "^1.8.1", + "prosemirror-state": "^1.3.1", + "prosemirror-transform": "^1.2.1", + "prosemirror-view": "^1.13.3" + } + }, + "node_modules/prosemirror-trailing-node": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/prosemirror-trailing-node/-/prosemirror-trailing-node-3.0.0.tgz", + "integrity": "sha512-xiun5/3q0w5eRnGYfNlW1uU9W6x5MoFKWwq/0TIRgt09lv7Hcser2QYV8t4muXbEr+Fwo0geYn79Xs4GKywrRQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@remirror/core-constants": "3.0.0", + "escape-string-regexp": "^4.0.0" + }, + "peerDependencies": { + "prosemirror-model": "^1.22.1", + "prosemirror-state": "^1.4.2", + "prosemirror-view": "^1.33.8" + } + }, + "node_modules/prosemirror-trailing-node/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/prosemirror-transform": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/prosemirror-transform/-/prosemirror-transform-1.10.2.tgz", + "integrity": "sha512-2iUq0wv2iRoJO/zj5mv8uDUriOHWzXRnOTVgCzSXnktS/2iQRa3UUQwVlkBlYZFtygw6Nh1+X4mGqoYBINn5KQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "prosemirror-model": "^1.21.0" + } + }, + "node_modules/prosemirror-view": { + "version": "1.37.0", + "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.37.0.tgz", + "integrity": "sha512-z2nkKI1sJzyi7T47Ji/ewBPuIma1RNvQCCYVdV+MqWBV7o4Sa1n94UJCJJ1aQRF/xRkFfyqLGlGFWitIcCOtbg==", + "license": "MIT", + "peer": true, + "dependencies": { + "prosemirror-model": "^1.20.0", + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.1.0" + } + }, "node_modules/protobufjs": { "version": "7.3.0", "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.3.0.tgz", @@ -18652,6 +19187,16 @@ "node": ">=6" } }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6" + } + }, "node_modules/qjobs": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.2.0.tgz", @@ -19305,6 +19850,13 @@ "fsevents": "~2.3.2" } }, + "node_modules/rope-sequence": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/rope-sequence/-/rope-sequence-1.3.4.tgz", + "integrity": "sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==", + "license": "MIT", + "peer": true + }, "node_modules/rtl-css-js": { "version": "1.16.1", "resolved": "https://registry.npmjs.org/rtl-css-js/-/rtl-css-js-1.16.1.tgz", @@ -20922,6 +21474,16 @@ "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==", "license": "MIT" }, + "node_modules/tippy.js": { + "version": "6.3.7", + "resolved": "https://registry.npmjs.org/tippy.js/-/tippy.js-6.3.7.tgz", + "integrity": "sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@popperjs/core": "^2.9.0" + } + }, "node_modules/tmp": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", @@ -21203,6 +21765,13 @@ "node": "*" } }, + "node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "license": "MIT", + "peer": true + }, "node_modules/uglify-js": { "version": "3.17.4", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", @@ -22303,6 +22872,13 @@ "eslint": ">=6.0.0" } }, + "node_modules/w3c-keyname": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", + "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", + "license": "MIT", + "peer": true + }, "node_modules/watchpack": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.1.tgz", diff --git a/package.json b/package.json index 4b2a2cb0..ba22646c 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,15 @@ "prismjs": "^1.29.0", "rxjs": "^7.4.0", "tslib": "^2.3.0", + "ngx-tiptap": "^11.1.0", + "@tiptap/extension-placeholder": "^2.7.1", + "@tiptap/extension-bold": "^2.7.1", + "@tiptap/extension-italic": "^2.7.1", + "@tiptap/extension-strike": "^2.7.1", + "@tiptap/extension-underline": "^2.7.1", + "@tiptap/extension-document": "^2.7.1", + "@tiptap/extension-text": "^2.7.1", + "@tiptap/extension-paragraph": "^2.7.1", "uuid": "^10.0.0", "zone.js": "~0.14.2" }, diff --git a/src/app/features/spaces/contents/contents.module.ts b/src/app/features/spaces/contents/contents.module.ts index 66049cbe..7f8e21a1 100644 --- a/src/app/features/spaces/contents/contents.module.ts +++ b/src/app/features/spaces/contents/contents.module.ts @@ -8,25 +8,27 @@ import { SchemaService } from '@shared/services/schema.service'; import { ContentService } from '@shared/services/content.service'; import { ContentHelperService } from '@shared/services/content-helper.service'; import { AssetService } from '@shared/services/asset.service'; -import { AddDocumentDialogComponent } from './add-document-dialog/add-document-dialog.component'; -import { AddFolderDialogComponent } from './add-folder-dialog/add-folder-dialog.component'; -import { EditDialogComponent } from './edit-dialog/edit-dialog.component'; +import { AddDocumentDialogComponent } from './add-document-dialog'; +import { AddFolderDialogComponent } from './add-folder-dialog'; +import { EditDialogComponent } from './edit-dialog'; import { EditDocumentComponent } from './edit-document/edit-document.component'; import { LinkSelectComponent } from './shared/link-select/link-select.component'; import { EditDocumentSchemaComponent } from './edit-document-schema/edit-document-schema.component'; import { AssetSelectComponent } from './shared/asset-select/asset-select.component'; import { AssetsSelectComponent } from './shared/assets-select/assets-select.component'; -import { ExportDialogComponent } from './export-dialog/export-dialog.component'; -import { ImportDialogComponent } from './import-dialog/import-dialog.component'; +import { ExportDialogComponent } from './export-dialog'; +import { ImportDialogComponent } from './import-dialog'; import { TaskService } from '@shared/services/task.service'; import { TokenService } from '@shared/services/token.service'; import { ReferenceSelectComponent } from './shared/reference-select/reference-select.component'; import { ReferencesSelectComponent } from './shared/references-select/references-select.component'; import { ContentHistoryService } from '@shared/services/content-history.service'; import { MarkdownModule } from 'ngx-markdown'; -import { MoveDialogComponent } from './move-dialog/move-dialog.component'; +import { MoveDialogComponent } from './move-dialog'; import { StatusComponent } from '@shared/components/status'; import { TranslateService } from '@shared/services/translate.service'; +import { NgxTiptapModule } from 'ngx-tiptap'; +import { RichTextEditorComponent } from './shared/rich-text-editor/rich-text-editor.component'; @NgModule({ declarations: [ @@ -44,8 +46,9 @@ import { TranslateService } from '@shared/services/translate.service'; ExportDialogComponent, ImportDialogComponent, MoveDialogComponent, + RichTextEditorComponent, ], - imports: [SharedModule, ContentsRoutingModule, MarkdownModule.forChild(), StatusComponent], + imports: [SharedModule, ContentsRoutingModule, MarkdownModule.forChild(), StatusComponent, NgxTiptapModule], providers: [ SpaceService, SchemaService, diff --git a/src/app/features/spaces/contents/edit-document-schema/edit-document-schema.component.html b/src/app/features/spaces/contents/edit-document-schema/edit-document-schema.component.html index f6dd7316..faf93a1a 100644 --- a/src/app/features/spaces/contents/edit-document-schema/edit-document-schema.component.html +++ b/src/app/features/spaces/contents/edit-document-schema/edit-document-schema.component.html @@ -88,6 +88,12 @@ } } + + @case ('RICH_TEXT') { + @if (form.controls[field.name]; as control) { + + } + } @case ('MARKDOWN') { diff --git a/src/app/features/spaces/contents/shared/rich-text-editor/rich-text-editor.component.html b/src/app/features/spaces/contents/shared/rich-text-editor/rich-text-editor.component.html new file mode 100644 index 00000000..4871d554 --- /dev/null +++ b/src/app/features/spaces/contents/shared/rich-text-editor/rich-text-editor.component.html @@ -0,0 +1,67 @@ +@if (component(); as field) { + {{ field.displayName || field.name }} +
+ + + + +   + +
+ + @if (field.description) { + {{ field.description }} + } + @if (field.maxLength) { + {{ form().value?.length || 0 }} /{{ field.maxLength }} + } + @if (form().errors; as errors) { + {{ fe.errors(errors) }} + } +} diff --git a/src/app/features/spaces/contents/shared/rich-text-editor/rich-text-editor.component.scss b/src/app/features/spaces/contents/shared/rich-text-editor/rich-text-editor.component.scss new file mode 100644 index 00000000..5fb4bf17 --- /dev/null +++ b/src/app/features/spaces/contents/shared/rich-text-editor/rich-text-editor.component.scss @@ -0,0 +1,4 @@ +.vertical-divider { + border-right: 1px solid var(--sys-outline); + @apply mx-1; +} diff --git a/src/app/features/spaces/contents/shared/rich-text-editor/rich-text-editor.component.ts b/src/app/features/spaces/contents/shared/rich-text-editor/rich-text-editor.component.ts new file mode 100644 index 00000000..053a4f2f --- /dev/null +++ b/src/app/features/spaces/contents/shared/rich-text-editor/rich-text-editor.component.ts @@ -0,0 +1,48 @@ +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, inject, input, OnDestroy } from '@angular/core'; +import { AbstractControl } from '@angular/forms'; +import { FormErrorHandlerService } from '@core/error-handler/form-error-handler.service'; +import { SchemaFieldRichText } from '@shared/models/schema.model'; +import { LocalSettingsStore } from '@shared/stores/local-settings.store'; +import { Editor } from '@tiptap/core'; +import Bold from '@tiptap/extension-bold'; +import Italic from '@tiptap/extension-italic'; +import Strike from '@tiptap/extension-strike'; +import Underline from '@tiptap/extension-underline'; +import Document from '@tiptap/extension-document'; +import Text from '@tiptap/extension-text'; +import Paragraph from '@tiptap/extension-paragraph'; +import Placeholder from '@tiptap/extension-placeholder'; + +@Component({ + selector: 'll-rich-text-editor', + templateUrl: './rich-text-editor.component.html', + styleUrls: ['./rich-text-editor.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class RichTextEditorComponent implements OnDestroy { + // Input + form = input.required(); + component = input.required(); + + //Settings + settingsStore = inject(LocalSettingsStore); + + editor = new Editor({ + extensions: [Document, Text, Paragraph, Bold, Italic, Strike, Underline, Placeholder], + editorProps: { + attributes: { + class: 'p-2 border-color border rounded-b-md outline-none', + spellcheck: 'false', + }, + }, + }); + + constructor( + readonly fe: FormErrorHandlerService, + private readonly cd: ChangeDetectorRef, + ) {} + + ngOnDestroy(): void { + this.editor.destroy(); + } +} diff --git a/src/app/features/spaces/schemas/shared/edit-field/edit-field.component.html b/src/app/features/spaces/schemas/shared/edit-field/edit-field.component.html index 6e385e77..ac953509 100644 --- a/src/app/features/spaces/schemas/shared/edit-field/edit-field.component.html +++ b/src/app/features/spaces/schemas/shared/edit-field/edit-field.component.html @@ -49,6 +49,7 @@ [ 'TEXT', 'TEXTAREA', + 'RICH_TEXT', 'MARKDOWN', 'NUMBER', 'COLOR', @@ -103,8 +104,8 @@
} - - @if (['TEXT', 'TEXTAREA', 'MARKDOWN'].includes(form.value.kind)) { + + @if (['TEXT', 'TEXTAREA', 'RICH_TEXT', 'MARKDOWN'].includes(form.value.kind)) { Min Length diff --git a/src/app/features/spaces/schemas/shared/edit-field/edit-field.component.ts b/src/app/features/spaces/schemas/shared/edit-field/edit-field.component.ts index 0153d97d..bb7ac875 100644 --- a/src/app/features/spaces/schemas/shared/edit-field/edit-field.component.ts +++ b/src/app/features/spaces/schemas/shared/edit-field/edit-field.component.ts @@ -81,6 +81,7 @@ export class EditFieldComponent implements OnInit { switch (value) { case SchemaFieldKind.TEXT: case SchemaFieldKind.TEXTAREA: + case SchemaFieldKind.RICH_TEXT: case SchemaFieldKind.MARKDOWN: { // ADD this.form.addControl('translatable', this.fb.control(undefined, SchemaValidator.FIELD_TRANSLATABLE)); @@ -109,7 +110,7 @@ export class EditFieldComponent implements OnInit { this.form.addControl('minValue', this.fb.control(undefined, SchemaValidator.FIELD_MIN_VALUE)); this.form.addControl('maxValue', this.fb.control(undefined, SchemaValidator.FIELD_MAX_VALUE)); // REMOVE - // Text & TextArea + // Text & TextArea & RichTex & Markdown this.form.removeControl('minLength'); this.form.removeControl('maxLength'); // Option & Options @@ -129,7 +130,7 @@ export class EditFieldComponent implements OnInit { // ADD this.form.addControl('translatable', this.fb.control(undefined, SchemaValidator.FIELD_TRANSLATABLE)); // REMOVE - // Text & TextArea + // Text & TextArea & RichTex & Markdown this.form.removeControl('minLength'); this.form.removeControl('maxLength'); // Number @@ -153,7 +154,7 @@ export class EditFieldComponent implements OnInit { // ADD this.form.addControl('translatable', this.fb.control(undefined, SchemaValidator.FIELD_TRANSLATABLE)); // REMOVE - // Text & TextArea + // Text & TextArea & RichTex & Markdown this.form.removeControl('minLength'); this.form.removeControl('maxLength'); // Number @@ -176,7 +177,7 @@ export class EditFieldComponent implements OnInit { // ADD this.form.addControl('translatable', this.fb.control(undefined, SchemaValidator.FIELD_TRANSLATABLE)); // REMOVE - // Text & TextArea + // Text & TextArea & RichTex & Markdown this.form.removeControl('minLength'); this.form.removeControl('maxLength'); // Number @@ -199,7 +200,7 @@ export class EditFieldComponent implements OnInit { // ADD this.form.addControl('translatable', this.fb.control(undefined, SchemaValidator.FIELD_TRANSLATABLE)); // REMOVE - // Text & TextArea + // Text & TextArea & RichTex & Markdown this.form.removeControl('minLength'); this.form.removeControl('maxLength'); // Number @@ -226,7 +227,7 @@ export class EditFieldComponent implements OnInit { this.form.addControl('options', options); this.form.addControl('source', this.fb.control('self', SchemaValidator.FIELD_OPTION_SOURCE)); // REMOVE - // Text & TextArea + // Text & TextArea & RichTex & Markdown this.form.removeControl('minLength'); this.form.removeControl('maxLength'); // Number @@ -253,7 +254,7 @@ export class EditFieldComponent implements OnInit { this.form.addControl('minValues', this.fb.control(undefined, SchemaValidator.FIELD_MIN_VALUES)); this.form.addControl('maxValues', this.fb.control(undefined, SchemaValidator.FIELD_MAX_VALUES)); // REMOVE - // Text & TextArea + // Text & TextArea & RichTex & Markdown this.form.removeControl('minLength'); this.form.removeControl('maxLength'); // Number @@ -271,7 +272,7 @@ export class EditFieldComponent implements OnInit { // ADD this.form.addControl('translatable', this.fb.control(undefined, SchemaValidator.FIELD_TRANSLATABLE)); // REMOVE - // Text & TextArea + // Text & TextArea & RichTex & Markdown this.form.removeControl('minLength'); this.form.removeControl('maxLength'); // Number @@ -296,7 +297,7 @@ export class EditFieldComponent implements OnInit { this.form.addControl('path', this.fb.control(undefined, SchemaValidator.FIELD_REFERENCE_PATH)); // REMOVE this.form.removeControl('translatable'); - // Text & TextArea + // Text & TextArea & RichTex & Markdown this.form.removeControl('minLength'); this.form.removeControl('maxLength'); // Number @@ -321,7 +322,7 @@ export class EditFieldComponent implements OnInit { this.fb.control([AssetFileType.ANY], SchemaValidator.FIELD_FILE_TYPES), ); // REMOVE - // Text & TextArea + // Text & TextArea & RichTex & Markdown this.form.removeControl('minLength'); this.form.removeControl('maxLength'); // Number @@ -346,7 +347,7 @@ export class EditFieldComponent implements OnInit { this.fb.control([AssetFileType.ANY], SchemaValidator.FIELD_FILE_TYPES), ); // REMOVE - // Text & TextArea + // Text & TextArea & RichTex & Markdown this.form.removeControl('minLength'); this.form.removeControl('maxLength'); // Number @@ -368,7 +369,7 @@ export class EditFieldComponent implements OnInit { this.form.addControl('schemas', this.fb.control(undefined)); // REMOVE this.form.removeControl('translatable'); - // Text & TextArea + // Text & TextArea & RichTex & Markdown this.form.removeControl('minLength'); this.form.removeControl('maxLength'); // Number @@ -390,7 +391,7 @@ export class EditFieldComponent implements OnInit { this.form.addControl('schemas', this.fb.control(undefined)); // REMOVE this.form.removeControl('translatable'); - // Text & TextArea + // Text & TextArea & RichTex & Markdown this.form.removeControl('minLength'); this.form.removeControl('maxLength'); // Number diff --git a/src/app/shared/models/schema.model.ts b/src/app/shared/models/schema.model.ts index 67344d3b..d894f2df 100644 --- a/src/app/shared/models/schema.model.ts +++ b/src/app/shared/models/schema.model.ts @@ -62,6 +62,7 @@ export interface SchemaEnumValue { export type SchemaField = | SchemaFieldText | SchemaFieldTextarea + | SchemaFieldRichText | SchemaFieldMarkdown | SchemaFieldNumber | SchemaFieldColor @@ -81,6 +82,7 @@ export type SchemaField = export enum SchemaFieldKind { TEXT = 'TEXT', TEXTAREA = 'TEXTAREA', + RICH_TEXT = 'RICH_TEXT', MARKDOWN = 'MARKDOWN', NUMBER = 'NUMBER', COLOR = 'COLOR', @@ -107,7 +109,12 @@ export interface FieldKindDescription { export const schemaFieldKindDescriptions: Record = { TEXT: { name: 'Text', icon: 'title', description: 'Short text field, titles or headlines' }, TEXTAREA: { name: 'TextArea', icon: 'rtt', description: 'Long text field, description' }, - MARKDOWN: { name: 'Markdown', icon: 'functions', description: 'Markdown text field, description' }, + RICH_TEXT: { + name: 'RichText', + icon: 'format_shapes', + description: 'Rich text field, text that includes formatting commands for page layout such as bold, underline, italic, etc.', + }, + MARKDOWN: { name: 'Markdown', icon: 'markdown', description: 'Markdown text field, description' }, NUMBER: { name: 'Number', icon: 'pin', description: 'Number field, amount or quantity' }, COLOR: { name: 'Color', icon: 'colorize', description: 'Color field, background or text color' }, DATE: { name: 'Date', icon: 'event', description: 'Date field, calendar date picker' }, @@ -146,6 +153,12 @@ export interface SchemaFieldTextarea extends SchemaFieldBase { maxLength?: number; } +export interface SchemaFieldRichText extends SchemaFieldBase { + kind: SchemaFieldKind.RICH_TEXT; + minLength?: number; + maxLength?: number; +} + export interface SchemaFieldMarkdown extends SchemaFieldBase { kind: SchemaFieldKind.MARKDOWN; minLength?: number; diff --git a/src/app/shared/services/content-helper.service.ts b/src/app/shared/services/content-helper.service.ts index 12af3f14..a1d028e8 100644 --- a/src/app/shared/services/content-helper.service.ts +++ b/src/app/shared/services/content-helper.service.ts @@ -235,6 +235,7 @@ export class ContentHelperService { switch (field.kind) { case SchemaFieldKind.TEXT: case SchemaFieldKind.TEXTAREA: + case SchemaFieldKind.RICH_TEXT: case SchemaFieldKind.MARKDOWN: { if (field.minLength) { validators.push(Validators.minLength(field.minLength)); diff --git a/src/styles.scss b/src/styles.scss index 16741799..8242ced5 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -18,6 +18,7 @@ @import 'styles/mat-snack-bar'; @import 'styles/mat-table'; @import 'styles/mat-toolbar'; +@import 'styles/rich-text-editor'; $primary: mat.m2-define-palette(mat.$m2-blue-palette, 900); $accent: mat.m2-define-palette(mat.$m2-pink-palette, A200, A100, A400); diff --git a/src/styles/_rich-text-editor.scss b/src/styles/_rich-text-editor.scss new file mode 100644 index 00000000..ada7b44c --- /dev/null +++ b/src/styles/_rich-text-editor.scss @@ -0,0 +1,7 @@ +@use '@angular/material' as mat; + +ll-rich-text-editor { + .border-color { + border-color: var(--sys-outline); + } +}