Skip to content

Commit

Permalink
Update Rich Text Editor
Browse files Browse the repository at this point in the history
  • Loading branch information
alexcibotari committed Nov 30, 2024
1 parent bd6dca0 commit 536df15
Show file tree
Hide file tree
Showing 9 changed files with 211 additions and 76 deletions.
57 changes: 57 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@
"@tiptap/extension-document": "^2.7.1",
"@tiptap/extension-text": "^2.7.1",
"@tiptap/extension-paragraph": "^2.7.1",
"@tiptap/extension-history": "^2.7.1",
"@tiptap/extension-list-item": "^2.7.1",
"@tiptap/extension-bullet-list": "^2.7.1",
"@tiptap/extension-ordered-list": "^2.7.1",
"uuid": "^10.0.0",
"zone.js": "~0.14.2"
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,67 +1,83 @@
@if (component(); as field) {
<mat-label>{{ field.displayName || field.name }}</mat-label>
<div class="border-color border border-b-0 rounded-t-md p-1 flex">
<button
class="rounded h-7 w-7 flex justify-center items-center ml-1"
[ngClass]="{ 'bg-black text-white': editor.isActive('bold') }"
(click)="editor.chain().focus().toggleBold().run()">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" class="bi bi-type-bold" viewBox="0 0 16 16">
<path
d="M8.21 13c2.106 0 3.412-1.087 3.412-2.823 0-1.306-.984-2.283-2.324-2.386v-.055a2.176 2.176 0 0 0 1.852-2.14c0-1.51-1.162-2.46-3.014-2.46H3.843V13zM5.908 4.674h1.696c.963 0 1.517.451 1.517 1.244 0 .834-.629 1.32-1.73 1.32H5.908V4.673zm0 6.788V8.598h1.73c1.217 0 1.88.492 1.88 1.415 0 .943-.643 1.449-1.832 1.449H5.907z" />
</svg>
</button>
<button
class="rounded h-7 w-7 flex justify-center items-center ml-1"
[ngClass]="{ 'bg-black text-white': editor.isActive('italic') }"
(click)="editor.chain().focus().toggleItalic().run()">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" class="bi bi-type-italic" viewBox="0 0 16 16">
<path
d="M7.991 11.674 9.53 4.455c.123-.595.246-.71 1.347-.807l.11-.52H7.211l-.11.52c1.06.096 1.128.212 1.005.807L6.57 11.674c-.123.595-.246.71-1.346.806l-.11.52h3.774l.11-.52c-1.06-.095-1.129-.211-1.006-.806z" />
</svg>
</button>
<button
class="rounded h-7 w-7 flex justify-center items-center ml-1"
[ngClass]="{ 'bg-black text-white': editor.isActive('strike') }"
(click)="editor.chain().focus().toggleStrike().run()">
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
fill="currentColor"
class="bi bi-type-strikethrough"
viewBox="0 0 16 16">
<path
d="M6.333 5.686c0 .31.083.581.27.814H5.166a2.8 2.8 0 0 1-.099-.76c0-1.627 1.436-2.768 3.48-2.768 1.969 0 3.39 1.175 3.445 2.85h-1.23c-.11-1.08-.964-1.743-2.25-1.743-1.23 0-2.18.602-2.18 1.607zm2.194 7.478c-2.153 0-3.589-1.107-3.705-2.81h1.23c.144 1.06 1.129 1.703 2.544 1.703 1.34 0 2.31-.705 2.31-1.675 0-.827-.547-1.374-1.914-1.675L8.046 8.5H1v-1h14v1h-3.504c.468.437.675.994.675 1.697 0 1.826-1.436 2.967-3.644 2.967" />
</svg>
</button>
<button
class="rounded h-7 w-7 flex justify-center items-center ml-1"
[ngClass]="{ 'bg-black text-white': editor.isActive('underline') }"
(click)="editor.chain().focus().toggleUnderline().run()">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" class="bi bi-type-underline" viewBox="0 0 16 16">
<path
d="M5.313 3.136h-1.23V9.54c0 2.105 1.47 3.623 3.917 3.623s3.917-1.518 3.917-3.623V3.136h-1.23v6.323c0 1.49-.978 2.57-2.687 2.57s-2.687-1.08-2.687-2.57zM12.5 15h-9v-1h9z" />
</svg>
</button>
<span class="vertical-divider">&nbsp; </span>
<button
class="rounded h-7 w-7 flex justify-center items-center ml-1"
[ngClass]="{ 'bg-black text-white': editor.isActive('underline') }"
(click)="editor.chain().focus().toggleUnderline().run()">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" class="bi bi-type-underline" viewBox="0 0 16 16">
<path
d="M5.313 3.136h-1.23V9.54c0 2.105 1.47 3.623 3.917 3.623s3.917-1.518 3.917-3.623V3.136h-1.23v6.323c0 1.49-.978 2.57-2.687 2.57s-2.687-1.08-2.687-2.57zM12.5 15h-9v-1h9z" />
</svg>
</button>
</div>
<tiptap-editor [editor]="editor" [formControl]="form()"></tiptap-editor>
@if (field.description) {
<mat-hint>{{ field.description }}</mat-hint>
<div class="my-4">
@if (component(); as field) {
<mat-label>{{ field.displayName || field.name }}</mat-label>
<div class="border-color border border-b-0 rounded-t-md p-1 flex">
<button
class="rounded h-7 w-7 flex justify-center items-center ml-1"
[ngClass]="{ 'bg-black text-white': editor.isActive('bold') }"
(click)="editor.chain().focus().toggleBold().run()">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" class="bi bi-type-bold" viewBox="0 0 16 16">
<path
d="M8.21 13c2.106 0 3.412-1.087 3.412-2.823 0-1.306-.984-2.283-2.324-2.386v-.055a2.176 2.176 0 0 0 1.852-2.14c0-1.51-1.162-2.46-3.014-2.46H3.843V13zM5.908 4.674h1.696c.963 0 1.517.451 1.517 1.244 0 .834-.629 1.32-1.73 1.32H5.908V4.673zm0 6.788V8.598h1.73c1.217 0 1.88.492 1.88 1.415 0 .943-.643 1.449-1.832 1.449H5.907z" />
</svg>
</button>
<button
class="rounded h-7 w-7 flex justify-center items-center ml-1"
[ngClass]="{ 'bg-black text-white': editor.isActive('italic') }"
(click)="editor.chain().focus().toggleItalic().run()">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" class="bi bi-type-italic" viewBox="0 0 16 16">
<path
d="M7.991 11.674 9.53 4.455c.123-.595.246-.71 1.347-.807l.11-.52H7.211l-.11.52c1.06.096 1.128.212 1.005.807L6.57 11.674c-.123.595-.246.71-1.346.806l-.11.52h3.774l.11-.52c-1.06-.095-1.129-.211-1.006-.806z" />
</svg>
</button>
<button
class="rounded h-7 w-7 flex justify-center items-center ml-1"
[ngClass]="{ 'bg-black text-white': editor.isActive('strike') }"
(click)="editor.chain().focus().toggleStrike().run()">
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
fill="currentColor"
class="bi bi-type-strikethrough"
viewBox="0 0 16 16">
<path
d="M6.333 5.686c0 .31.083.581.27.814H5.166a2.8 2.8 0 0 1-.099-.76c0-1.627 1.436-2.768 3.48-2.768 1.969 0 3.39 1.175 3.445 2.85h-1.23c-.11-1.08-.964-1.743-2.25-1.743-1.23 0-2.18.602-2.18 1.607zm2.194 7.478c-2.153 0-3.589-1.107-3.705-2.81h1.23c.144 1.06 1.129 1.703 2.544 1.703 1.34 0 2.31-.705 2.31-1.675 0-.827-.547-1.374-1.914-1.675L8.046 8.5H1v-1h14v1h-3.504c.468.437.675.994.675 1.697 0 1.826-1.436 2.967-3.644 2.967" />
</svg>
</button>
<button
class="rounded h-7 w-7 flex justify-center items-center ml-1"
[ngClass]="{ 'bg-black text-white': editor.isActive('underline') }"
(click)="editor.chain().focus().toggleUnderline().run()">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" class="bi bi-type-underline" viewBox="0 0 16 16">
<path
d="M5.313 3.136h-1.23V9.54c0 2.105 1.47 3.623 3.917 3.623s3.917-1.518 3.917-3.623V3.136h-1.23v6.323c0 1.49-.978 2.57-2.687 2.57s-2.687-1.08-2.687-2.57zM12.5 15h-9v-1h9z" />
</svg>
</button>
<span class="vertical-divider">&nbsp; </span>
<button
class="rounded h-7 w-7 flex justify-center items-center ml-1"
[ngClass]="{ 'bg-black text-white': editor.isActive('bulletList') }"
(click)="editor.chain().focus().toggleBulletList().run()">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" class="bi bi-list-ul" viewBox="0 0 16 16">
<path
fill-rule="evenodd"
d="M5 11.5a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5m0-4a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5m0-4a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5m-3 1a1 1 0 1 0 0-2 1 1 0 0 0 0 2m0 4a1 1 0 1 0 0-2 1 1 0 0 0 0 2m0 4a1 1 0 1 0 0-2 1 1 0 0 0 0 2" />
</svg>
</button>
<button
class="rounded h-7 w-7 flex justify-center items-center ml-1"
[ngClass]="{ 'bg-black text-white': editor.isActive('orderedList') }"
(click)="editor.chain().focus().toggleOrderedList().run()">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" class="bi bi-list-ol" viewBox="0 0 16 16">
<path
fill-rule="evenodd"
d="M5 11.5a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5m0-4a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5m0-4a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5" />
<path
d="M1.713 11.865v-.474H2c.217 0 .363-.137.363-.317 0-.185-.158-.31-.361-.31-.223 0-.367.152-.373.31h-.59c.016-.467.373-.787.986-.787.588-.002.954.291.957.703a.595.595 0 0 1-.492.594v.033a.615.615 0 0 1 .569.631c.003.533-.502.8-1.051.8-.656 0-1-.37-1.008-.794h.582c.008.178.186.306.422.309.254 0 .424-.145.422-.35-.002-.195-.155-.348-.414-.348h-.3zm-.004-4.699h-.604v-.035c0-.408.295-.844.958-.844.583 0 .96.326.96.756 0 .389-.257.617-.476.848l-.537.572v.03h1.054V9H1.143v-.395l.957-.99c.138-.142.293-.304.293-.508 0-.18-.147-.32-.342-.32a.33.33 0 0 0-.342.338zM2.564 5h-.635V2.924h-.031l-.598.42v-.567l.629-.443h.635z" />
</svg>
</button>
<span class="vertical-divider">&nbsp; </span>
</div>
<tiptap-editor [editor]="editor" [formControl]="form()"></tiptap-editor>
@if (field.description) {
<mat-hint>{{ field.description }}</mat-hint>
}
@if (field.maxLength) {
<mat-hint align="end"> {{ form().value?.length || 0 }} /{{ field.maxLength }}</mat-hint>
}
@if (form().errors; as errors) {
<mat-error>{{ fe.errors(errors) }}</mat-error>
}
}
@if (field.maxLength) {
<mat-hint align="end"> {{ form().value?.length || 0 }} /{{ field.maxLength }}</mat-hint>
}
@if (form().errors; as errors) {
<mat-error>{{ fe.errors(errors) }}</mat-error>
}
}
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ import Document from '@tiptap/extension-document';
import Text from '@tiptap/extension-text';
import Paragraph from '@tiptap/extension-paragraph';
import Placeholder from '@tiptap/extension-placeholder';
import History from '@tiptap/extension-history';
import ListItem from '@tiptap/extension-list-item';
import OrderedList from '@tiptap/extension-ordered-list';
import BulletList from '@tiptap/extension-bullet-list';

@Component({
selector: 'll-rich-text-editor',
Expand All @@ -28,7 +32,7 @@ export class RichTextEditorComponent implements OnDestroy {
settingsStore = inject(LocalSettingsStore);

editor = new Editor({
extensions: [Document, Text, Paragraph, Bold, Italic, Strike, Underline, Placeholder],
extensions: [Document, Text, Paragraph, Bold, Italic, Strike, Underline, Placeholder, History, ListItem, OrderedList, BulletList],
editorProps: {
attributes: {
class: 'p-2 border-color border rounded-b-md outline-none',
Expand All @@ -40,7 +44,8 @@ export class RichTextEditorComponent implements OnDestroy {
constructor(
readonly fe: FormErrorHandlerService,
private readonly cd: ChangeDetectorRef,
) {}
) {

Check failure on line 47 in src/app/features/spaces/contents/shared/rich-text-editor/rich-text-editor.component.ts

View workflow job for this annotation

GitHub Actions / Lint and Build

Delete `⏎··`
}

ngOnDestroy(): void {
this.editor.destroy();
Expand Down
34 changes: 31 additions & 3 deletions src/app/features/spaces/schemas/edit-comp/edit-comp.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,41 @@
{{ field.value.displayName }} <span class="field-id">#{{ field.value.name }}</span>
</span>
<span matListItemLine>
<span class="badge">{{ field.value.kind }}</span>
<span class="badge">{{ descriptor.name }}</span>
@if (field.value.translatable) {
&nbsp;<span class="badge">Translatable</span>
<span class="badge">Translatable</span>
}
@if (field.value.required) {
&nbsp;<span class="badge">Required</span>
<span class="badge">Required</span>
}
@if (field.value.fileTypes) {
<span class="badge">File Type: {{ field.value.fileTypes }}</span>
}
@if (field.value.path) {
<span class="badge">Path: {{ field.value.path }}</span>
}
@if (field.value.source) {
<span class="badge">Path: {{ field.value.source }}</span>
}
@if (field.value.minLength) {
<span class="badge">Min Length: {{ field.value.minLength }}</span>
}
@if (field.value.maxLength) {
<span class="badge">Max Length: {{ field.value.maxLength }}</span>
}
@if (field.value.minValue) {
<span class="badge">Min Value: {{ field.value.minValue }}</span>
}
@if (field.value.maxValue) {
<span class="badge">Max Value: {{ field.value.maxValue }}</span>
}
@if (field.value.minValues) {
<span class="badge">Min Values: {{ field.value.minValues }}</span>
}
@if (field.value.maxValues) {
<span class="badge">Max Values: {{ field.value.maxValues }}</span>
}

Check failure on line 87 in src/app/features/spaces/schemas/edit-comp/edit-comp.component.html

View workflow job for this annotation

GitHub Actions / Lint and Build

Delete `⏎`
</span>
<div matListItemMeta>
<button mat-icon-button (click)="removeComponent($event, index)">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ mat-form-field {
}

.badge {
@apply inline-flex items-center rounded-md bg-indigo-50 px-2 py-1 text-xs font-medium text-indigo-700 ring-1 ring-inset ring-indigo-700/10
@apply inline-flex items-center rounded-md bg-indigo-50 px-2 py-1 mx-1 text-xs font-medium text-indigo-700 ring-1 ring-inset ring-indigo-700/10
}

.title {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ export class EditCompComponent implements OnInit, DirtyFormGuardComponent {
switch (element?.kind) {
case SchemaFieldKind.TEXT:
case SchemaFieldKind.TEXTAREA:
case SchemaFieldKind.RICH_TEXT:
case SchemaFieldKind.MARKDOWN: {
fieldForm.addControl(
'translatable',
Expand Down
8 changes: 4 additions & 4 deletions src/app/shared/models/schema.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,9 +108,9 @@ export interface FieldKindDescription {

export const schemaFieldKindDescriptions: Record<SchemaFieldKind, FieldKindDescription> = {
TEXT: { name: 'Text', icon: 'title', description: 'Short text field, titles or headlines' },
TEXTAREA: { name: 'TextArea', icon: 'rtt', description: 'Long text field, description' },
TEXTAREA: { name: 'Text Area', icon: 'rtt', description: 'Long text field, description' },
RICH_TEXT: {
name: 'RichText',
name: 'Rich Text',
icon: 'format_shapes',
description: 'Rich text field, text that includes formatting commands for page layout such as bold, underline, italic, etc.',
},
Expand All @@ -123,8 +123,8 @@ export const schemaFieldKindDescriptions: Record<SchemaFieldKind, FieldKindDescr
OPTION: { name: 'Option (One)', icon: 'list', description: 'Single selection field, dropdown' },
OPTIONS: { name: 'Options (Multiple)', icon: 'list', description: 'Multiple selection field, dropdown' },
LINK: { name: 'Link', icon: 'link', description: 'Link field, external URL or internal resource' },
REFERENCE: { name: 'Reference (One)', icon: 'link', description: 'Reference field, to a internal resource' },
REFERENCES: { name: 'References (Multiple)', icon: 'link', description: 'References field, to multiple internal resources' },
REFERENCE: { name: 'Reference (One)', icon: 'dataset_linked', description: 'Reference field, to a internal resource' },
REFERENCES: { name: 'References (Multiple)', icon: 'dataset_linked', description: 'References field, to multiple internal resources' },
ASSET: { name: 'Asset (One)', icon: 'attachment', description: 'Asset field, image, video or file' },
ASSETS: { name: 'Assets (Multiple)', icon: 'attachment', description: 'Assets field, multiple images, videos or files' },
SCHEMA: { name: 'Schema (One)', icon: 'polyline', description: 'Schema field, to a internal schema' },
Expand Down
24 changes: 24 additions & 0 deletions src/styles/_rich-text-editor.scss
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,28 @@ ll-rich-text-editor {
.border-color {
border-color: var(--sys-outline);
}

.tiptap {
:first-child {
margin-top: 0;
}

/* List styles */
ul {
list-style: disc;
}
ol {
list-style: decimal;
}
ul,
ol {
padding: 0 1rem;
margin: 1.25rem 1rem 1.25rem 0.4rem;

li p {
margin-top: 0.25em;
margin-bottom: 0.25em;
}
}
}
}

0 comments on commit 536df15

Please sign in to comment.