Skip to content

Commit

Permalink
feat(addon-editor): support audio/video tag in editor (#3585)
Browse files Browse the repository at this point in the history
Co-authored-by: tinkoff-bot <tinkoff-bot@users.noreply.github.com>
  • Loading branch information
splincode and tinkoff-bot committed Feb 6, 2023
1 parent 3018ed2 commit 5f65b72
Show file tree
Hide file tree
Showing 18 changed files with 316 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,14 @@
}
}

/* stylelint-disable-next-line */
.ProseMirror {
video,
audio {
pointer-events: none;
}
}

/* stylelint-disable-next-line */
.tableWrapper,
.tui-table-wrapper {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export class TuiEditorComponent
tools: readonly TuiEditorTool[] = defaultEditorTools;

@Output()
fileAttached = new EventEmitter<TuiEditorAttachedFile[]>();
fileAttached = new EventEmitter<Array<TuiEditorAttachedFile<any>>>();

@ViewChild(TuiToolbarComponent)
readonly toolbar?: TuiToolbarComponent;
Expand Down
1 change: 1 addition & 0 deletions projects/addon-editor/extensions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export * from '@taiga-ui/addon-editor/extensions/iframe-editor';
export * from '@taiga-ui/addon-editor/extensions/image-editor';
export * from '@taiga-ui/addon-editor/extensions/indent-outdent';
export * from '@taiga-ui/addon-editor/extensions/link';
export * from '@taiga-ui/addon-editor/extensions/media';
export * from '@taiga-ui/addon-editor/extensions/starter-kit';
export * from '@taiga-ui/addon-editor/extensions/table';
export * from '@taiga-ui/addon-editor/extensions/table-cell-background';
Expand Down
8 changes: 8 additions & 0 deletions projects/addon-editor/extensions/link/link.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
import {tuiParseNodeAttributes} from '@taiga-ui/addon-editor/utils';
import {getHTMLFromFragment} from '@tiptap/core';
import {Link} from '@tiptap/extension-link';

export const TuiLink = Link.extend({
addAttributes() {
return {
...this.parent?.(),
...tuiParseNodeAttributes([`download`]),
};
},

addCommands() {
return {
...this.parent?.(),
Expand Down
33 changes: 33 additions & 0 deletions projects/addon-editor/extensions/media/audio.extension.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import {tuiGetNestedNodes, tuiParseNodeAttributes} from '@taiga-ui/addon-editor/utils';
import {Node} from '@tiptap/core';
import {MarkSpec} from 'prosemirror-model';

export const TuiAudio = Node.create({
name: `audio`,
group: `block`,
content: `source+`,

addAttributes() {
return tuiParseNodeAttributes([
`id`,
`class`,
`src`,
`style`,
`controls`,
`loop`,
`muted`,
`preload`,
`autoplay`,
`width`,
`height`,
]);
},

parseHTML(): MarkSpec['parseDOM'] {
return [{tag: `audio`}];
},

renderHTML({node, HTMLAttributes}) {
return [`audio`, HTMLAttributes, ...tuiGetNestedNodes(node)];
},
});
3 changes: 3 additions & 0 deletions projects/addon-editor/extensions/media/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './audio.extension';
export * from './source.extension';
export * from './video.extension';
8 changes: 8 additions & 0 deletions projects/addon-editor/extensions/media/ng-package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"lib": {
"entryFile": "index.ts",
"styleIncludePaths": [
"../../../core/styles"
]
}
}
27 changes: 27 additions & 0 deletions projects/addon-editor/extensions/media/source.extension.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import {tuiParseNodeAttributes} from '@taiga-ui/addon-editor/utils';
import {mergeAttributes, Node} from '@tiptap/core';
import {MarkSpec} from 'prosemirror-model';

export const TuiSource = Node.create({
name: `source`,

addAttributes() {
return tuiParseNodeAttributes([
`src`,
`type`,
`width`,
`height`,
`media`,
`sizes`,
`srcset`,
]);
},

parseHTML(): MarkSpec['parseDOM'] {
return [{tag: `source`}];
},

renderHTML({HTMLAttributes}: Record<string, any>) {
return [`source`, mergeAttributes(HTMLAttributes)];
},
});
33 changes: 33 additions & 0 deletions projects/addon-editor/extensions/media/video.extension.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import {tuiGetNestedNodes, tuiParseNodeAttributes} from '@taiga-ui/addon-editor/utils';
import {Node} from '@tiptap/core';
import {MarkSpec} from 'prosemirror-model';

export const TuiVideo = Node.create({
name: `video`,
group: `block`,
content: `source+`,

addAttributes() {
return tuiParseNodeAttributes([
`id`,
`class`,
`src`,
`style`,
`controls`,
`loop`,
`muted`,
`preload`,
`autoplay`,
`width`,
`height`,
]);
},

parseHTML(): MarkSpec['parseDOM'] {
return [{tag: `video`}];
},

renderHTML({node, HTMLAttributes}) {
return [`video`, HTMLAttributes, ...tuiGetNestedNodes(node)];
},
});
3 changes: 2 additions & 1 deletion projects/addon-editor/interfaces/attached.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export interface TuiEditorAttachedFile {
export interface TuiEditorAttachedFile<T = Record<string, any>> {
name: string;
link: string;
attrs?: T;
}

export interface TuiEditorAttachOptions {
Expand Down
14 changes: 14 additions & 0 deletions projects/addon-editor/utils/get-nested-nodes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import {Attrs, Node as NodeElement} from 'prosemirror-model';

export function tuiGetNestedNodes(node: NodeElement): Array<Array<Attrs | string>> {
const nodes: Array<Array<Attrs | string>> = [];

// @note: the content field is not array type
node.content.forEach(child => {
if (child instanceof NodeElement) {
nodes.push([child.type.name, child.attrs]);
}
});

return nodes;
}
2 changes: 2 additions & 0 deletions projects/addon-editor/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ export * from './delete-nodes';
export * from './get-element-point';
export * from './get-gradient-data';
export * from './get-mark-range';
export * from './get-nested-nodes';
export * from './get-selected-content';
export * from './insert-html';
export * from './insert-text';
export * from './is-selection-in';
export * from './legacy-converter';
export * from './parse-gradient';
export * from './parse-node-attributes';
export * from './parse-style';
export * from './safe-link-range';
export * from './to-gradient';
13 changes: 13 additions & 0 deletions projects/addon-editor/utils/parse-node-attributes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import {Attribute} from '@tiptap/core';

export function tuiParseNodeAttributes(
attrs: string[],
): Record<string, Partial<Attribute>> {
return attrs.reduce((result, attribute) => {
result[attribute] = {
parseHTML: element => element?.getAttribute(`${attribute}`),
};

return result;
}, {} as Record<string, Partial<Attribute>>);
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,14 @@
>
<tui-editor-embed-example-2></tui-editor-embed-example-2>
</tui-doc-example>

<tui-doc-example
id="video-audio"
i18n-heading
heading="Video and audio nodes"
[content]="example3"
>
<tui-editor-embed-example-3></tui-editor-embed-example-3>
</tui-doc-example>
</ng-template>
</tui-doc-page>
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,9 @@ export class ExampleTuiEditorEmbedComponent {
'./examples/2/embed-tool/embed-tool.module.ts?raw'
),
};

readonly example3: TuiDocExample = {
HTML: import('./examples/3/index.html?raw'),
TypeScript: import('./examples/3/index.ts?raw'),
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {TuiEditorEmbedExample1} from './examples/1';
import {ExampleTuiYoutubeToolModule} from './examples/1/youtube-tool/youtube-tool.module';
import {TuiEditorEmbedExample2} from './examples/2';
import {ExampleTuiEmbedToolModule} from './examples/2/embed-tool/embed-tool.module';
import {TuiEditorEmbedExample3} from './examples/3';

@NgModule({
imports: [
Expand All @@ -39,6 +40,7 @@ import {ExampleTuiEmbedToolModule} from './examples/2/embed-tool/embed-tool.modu
ExampleTuiEditorEmbedComponent,
TuiEditorEmbedExample1,
TuiEditorEmbedExample2,
TuiEditorEmbedExample3,
],
})
export class ExampleTuiEditorEmbedModule {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<tui-editor
class="editor"
[formControl]="control"
[tools]="builtInTools"
(fileAttached)="attach($event)"
></tui-editor>

<h4>HTML:</h4>
<div [innerHTML]="safe(control.value)"></div>

<h4>Text:</h4>
<p>{{ control.value }}</p>
135 changes: 135 additions & 0 deletions projects/demo/src/modules/components/editor/embed/examples/3/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import {Component, Inject, Injector, ViewChild} from '@angular/core';
import {FormControl, Validators} from '@angular/forms';
import {DomSanitizer, SafeHtml} from '@angular/platform-browser';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {
TUI_ATTACH_FILES_LOADER,
TUI_ATTACH_FILES_OPTIONS,
TUI_EDITOR_EXTENSIONS,
TuiEditorAttachedFile,
TuiEditorComponent,
TuiEditorTool,
} from '@taiga-ui/addon-editor';
import {tuiPure, tuiTypedFromEvent} from '@taiga-ui/cdk';
import {Observable} from 'rxjs';
import {map} from 'rxjs/operators';

@Component({
selector: 'tui-editor-embed-example-3',
templateUrl: './index.html',
providers: [
{
provide: TUI_EDITOR_EXTENSIONS,
deps: [Injector],
useFactory: (_injector: Injector) => [
import('@taiga-ui/addon-editor/extensions/starter-kit').then(
({StarterKit}) => StarterKit,
),
import('@tiptap/extension-text-style').then(({TextStyle}) => TextStyle),
import('@taiga-ui/addon-editor/extensions/link').then(
({TuiLink}) => TuiLink,
),
import('@taiga-ui/addon-editor/extensions/jump-anchor').then(
({TuiJumpAnchor}) => TuiJumpAnchor,
),
import('@taiga-ui/addon-editor/extensions/file-link').then(
({TuiFileLink}) => TuiFileLink,
),
import('@taiga-ui/addon-editor/extensions/media').then(
({TuiVideo}) => TuiVideo,
),
import('@taiga-ui/addon-editor/extensions/media').then(
({TuiAudio}) => TuiAudio,
),
import('@taiga-ui/addon-editor/extensions/media').then(
({TuiSource}) => TuiSource,
),
],
},
{
provide: TUI_ATTACH_FILES_LOADER,
deps: [],
useFactory:
() =>
([file]: File[]): Observable<
Array<TuiEditorAttachedFile<{type: string}>>
> => {
const fileReader = new FileReader();

// For example, instead of uploading to a file server,
// we convert the result immediately into content to base64
fileReader.readAsDataURL(file);

return tuiTypedFromEvent(fileReader, 'load').pipe(
map(() => [
{
name: file.name,

/* base64 or link to the file on your server */
link: String(fileReader.result),

attrs: {
type: file.type,
},
},
]),
);
},
},
{
provide: TUI_ATTACH_FILES_OPTIONS,
useValue: {
accept: 'video/mp4,video/x-m4v,video/*,audio/x-m4a,audio/*',
multiple: false,
},
},
],
changeDetection,
encapsulation,
})
export class TuiEditorEmbedExample3 {
@ViewChild(TuiEditorComponent)
private readonly wysiwyg?: TuiEditorComponent;

readonly builtInTools = [
TuiEditorTool.Undo,
TuiEditorTool.Link,
TuiEditorTool.Attach,
];

readonly control = new FormControl(
`
<p>Here is video: </p>
<video controls="controls" width="100%">
<source src="https://www.w3schools.com/html/mov_bbb.mp4" type="video/mp4">
</video>
<p>Here is audio: </p>
<audio controls style="width: 100%">
<source src="https://www.w3docs.com/build/audios/audio.mp3" type="audio/mp3">
</audio>
<p></p>
`,
Validators.required,
);

constructor(@Inject(DomSanitizer) private readonly sanitizer: DomSanitizer) {}

@tuiPure
safe(content: string): SafeHtml {
return this.sanitizer.bypassSecurityTrustHtml(content);
}

attach([file]: Array<TuiEditorAttachedFile<{type: string}>>): void {
const tag = `${file.attrs?.type ?? ''}`.split('/')[0];

this.wysiwyg?.editor
?.getOriginTiptapEditor()
.commands.insertContent(
`<${tag} controls width="100%"><source src="${file.link}" type="${file.attrs?.type}"></${tag}><p><a href="${file.link}" download="${file.name}">Download ${file.name}</a></p>`,
);
}
}

0 comments on commit 5f65b72

Please sign in to comment.