Skip to content

Commit

Permalink
#26556 Detect if the AI plugin is installed and enable/disabled the A…
Browse files Browse the repository at this point in the history
…I Extensions in Block Editor (#27006)

* feat(block-editor) detect if the AI plugin is installed and turn on/off load AI Extensions

* feat(block-editor) fix comments

* feat(block-editor) rebase from master, rebuild block-editor file
  • Loading branch information
oidacra authored Dec 14, 2023
1 parent 9f674c4 commit 2bfe62e
Show file tree
Hide file tree
Showing 13 changed files with 207 additions and 74 deletions.
8 changes: 8 additions & 0 deletions core-web/apps/dotcms-ui/.storybook/middleware.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,14 @@ module.exports = function expressMiddleware(router) {
})
);

router.use(
'/api/v1/ai/image/test',
createProxyMiddleware({
target: 'http://localhost:8080',
changeOrigin: true
})
);

// Publish the image_temp generated
router.use(
'/api/v1/workflow/actions/default/fire/PUBLISH',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import { ButtonModule } from 'primeng/button';
import { CheckboxModule } from 'primeng/checkbox';
import { TabViewModule } from 'primeng/tabview';

import { DotApiLinkModule } from '@components/dot-api-link/dot-api-link.module';
import { DotMessageService } from '@dotcms/data-access';
import { DotApiLinkComponent } from '@dotcms/ui';
import { MockDotMessageService } from '@dotcms/utils-testing';

import { DotPortletBaseComponent } from './dot-portlet-base.component';
Expand All @@ -35,7 +35,7 @@ export default {
ButtonModule,
CheckboxModule,
DotPortletBaseModule,
DotApiLinkModule,
DotApiLinkComponent,
TabViewModule
]
}),
Expand Down
25 changes: 24 additions & 1 deletion core-web/libs/block-editor/src/lib/block-editor.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,24 @@ import {
} from './extensions';
import { AssetFormModule } from './extensions/asset-form/asset-form.module';
import { ContentletBlockComponent } from './nodes';
import { DotAiService, DotUploadFileService, EditorDirective } from './shared';
import {
AI_PLUGIN_INSTALLED_TOKEN,
DotAiService,
DotUploadFileService,
EditorDirective
} from './shared';
import { PrimengModule } from './shared/primeng.module';
import { DotBlockEditorInitService } from './shared/services/dot-block-editor-init/dot-block-editor-init.service';
import { SharedModule } from './shared/shared.module';

const initTranslations = (dotMessageService: DotMessageService) => {
return () => dotMessageService.init();
};

const initializeBlockEditor = (appInitService: DotBlockEditorInitService) => {
return () => appInitService.initializeBlockEditor();
};

@NgModule({
imports: [
CommonModule,
Expand Down Expand Up @@ -68,11 +78,24 @@ const initTranslations = (dotMessageService: DotMessageService) => {
LoggerService,
StringUtils,
DotAiService,
DotBlockEditorInitService,
{
provide: APP_INITIALIZER,
useFactory: initTranslations,
deps: [DotMessageService],
multi: true
},

{
provide: APP_INITIALIZER,
useFactory: initializeBlockEditor,
deps: [DotBlockEditorInitService],
multi: true
},
{
provide: AI_PLUGIN_INSTALLED_TOKEN,
useFactory: (service: DotBlockEditorInitService) => service.isPluginInstalled,
deps: [DotBlockEditorInitService]
}
],
exports: [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import {
} from '../../shared';
import { DotMessageServiceMock } from '../../shared/mocks/dot-message.service.mock';
import { DotAiServiceMock } from '../../shared/services/dot-ai/dot-ai-service.mock';
import { DotBlockEditorInitService } from '../../shared/services/dot-block-editor-init/dot-block-editor-init.service';

export default {
title: 'Library/Block Editor'
Expand Down Expand Up @@ -202,7 +203,8 @@ export const primary = () => ({
process.env.USE_MIDDLEWARE === 'true'
? DotMessageService
: DotMessageServiceMock
}
},
DotBlockEditorInitService
],
// We need these here because they are dynamically rendered
entryComponents: [
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import { Observable, Subject, combineLatest, from } from 'rxjs';
import { assert, object, string, array, optional } from 'superstruct';
import { combineLatest, from, Observable, Subject } from 'rxjs';
import { array, assert, object, optional, string } from 'superstruct';

import {
ChangeDetectorRef,
Component,
EventEmitter,
forwardRef,
inject,
Injector,
Input,
OnDestroy,
OnInit,
Output,
ViewContainerRef,
forwardRef,
inject
ViewContainerRef
} from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';

Expand All @@ -33,42 +33,43 @@ import StarterKit, { StarterKitOptions } from '@tiptap/starter-kit';

import { DotPropertiesService } from '@dotcms/data-access';
import {
RemoteCustomExtensions,
DotCMSContentlet,
DotCMSContentTypeField,
EDITOR_MARKETING_KEYS,
IMPORT_RESULTS,
DotCMSContentlet,
DotCMSContentTypeField
RemoteCustomExtensions
} from '@dotcms/dotcms-models';

import {
ActionsMenu,
AIContentActionsExtension,
AIContentPromptExtension,
AIImagePromptExtension,
AssetUploader,
BubbleAssetFormExtension,
BubbleFormExtension,
BubbleLinkFormExtension,
DotBubbleMenuExtension,
DEFAULT_LANG_ID,
DotBubbleMenuExtension,
DotComands,
DotConfigExtension,
DotFloatingButton,
DotTableCellExtension,
DotTableHeaderExtension,
DotTableExtension,
DotTableHeaderExtension,
DragHandler,
DotFloatingButton,
BubbleAssetFormExtension,
FreezeScroll,
FREEZE_SCROLL_KEY,
AssetUploader,
DotComands,
AIContentPromptExtension,
AIImagePromptExtension,
AIContentActionsExtension
FreezeScroll
} from '../../extensions';
import { DotPlaceholder } from '../../extensions/dot-placeholder/dot-placeholder-plugin';
import { ContentletBlock, ImageNode, VideoNode, AIContentNode, LoaderNode } from '../../nodes';
import { AIContentNode, ContentletBlock, ImageNode, LoaderNode, VideoNode } from '../../nodes';
import {
AI_PLUGIN_INSTALLED_TOKEN,
DotMarketingConfigService,
formatHTML,
removeInvalidNodes,
SetDocAttrStep,
DotMarketingConfigService,
RestoreDefaultDOMAttrs
RestoreDefaultDOMAttrs,
SetDocAttrStep
} from '../../shared';

@Component({
Expand All @@ -91,10 +92,18 @@ export class DotBlockEditorComponent implements OnInit, OnDestroy, ControlValueA
@Input() isFullscreen = false;
@Input() value: Content = '';
@Output() valueChange = new EventEmitter<JSONContent>();

public allowedContentTypes: string;
public customStyles: string;
public displayCountBar: boolean | string = true;
public charLimit: number;
public customBlocks = '';
public content: Content = '';
public contentletIdentifier: string;
editor: Editor;
subject = new Subject();
freezeScroll = true;
private onChange: (value: string) => void;
private onTouched: () => void;

private destroy$: Subject<boolean> = new Subject<boolean>();
private allowedBlocks: string[] = ['paragraph']; //paragraph should be always.
private _customNodes: Map<string, AnyExtension> = new Map([
Expand All @@ -105,18 +114,15 @@ export class DotBlockEditorComponent implements OnInit, OnDestroy, ControlValueA
['aiContent', AIContentNode],
['loader', LoaderNode]
]);
private readonly cd = inject(ChangeDetectorRef);
private readonly dotPropertiesService = inject(DotPropertiesService);
private readonly isAIPluginInstalled: boolean = inject(AI_PLUGIN_INSTALLED_TOKEN);

public allowedContentTypes: string;
public customStyles: string;
public displayCountBar: boolean | string = true;
public charLimit: number;
public customBlocks = '';
public content: Content = '';
public contentletIdentifier: string;

editor: Editor;
subject = new Subject();
freezeScroll = true;
constructor(
private readonly injector: Injector,
private readonly viewContainerRef: ViewContainerRef,
private readonly dotMarketingConfigService: DotMarketingConfigService
) {}

get characterCount(): CharacterCountStorage {
return this.editor?.storage.characterCount;
Expand All @@ -136,15 +142,6 @@ export class DotBlockEditorComponent implements OnInit, OnDestroy, ControlValueA
return Math.ceil(this.characterCount.words() / 265);
}

private readonly cd = inject(ChangeDetectorRef);
private readonly dotPropertiesService = inject(DotPropertiesService);

constructor(
private readonly injector: Injector,
private readonly viewContainerRef: ViewContainerRef,
private readonly dotMarketingConfigService: DotMarketingConfigService
) {}

registerOnChange(fn: (value: string) => void) {
this.onChange = fn;
}
Expand Down Expand Up @@ -411,14 +408,13 @@ export class DotBlockEditorComponent implements OnInit, OnDestroy, ControlValueA
}

/**
* Extensions that improve the user experience
* Returns an array of editor extensions
*
* @private
* @return {*}
* @memberof DotBlockEditorComponent
* @returns {Array} An array of editor extensions
*/
private getEditorExtensions() {
return [
const extensions = [
DotConfigExtension({
lang: this.languageId || this.contentlet?.languageId,
allowedContentTypes: this.allowedContentTypes,
Expand All @@ -436,14 +432,13 @@ export class DotBlockEditorComponent implements OnInit, OnDestroy, ControlValueA
}),
Subscript,
Superscript,
ActionsMenu(this.viewContainerRef, this.getParsedCustomBlocks()),
ActionsMenu(this.viewContainerRef, this.getParsedCustomBlocks(), {
shouldShowAIExtensions: this.isAIPluginInstalled
}),
DragHandler(this.viewContainerRef),
BubbleLinkFormExtension(this.viewContainerRef, this.languageId),
DotBubbleMenuExtension(this.viewContainerRef),
BubbleFormExtension(this.viewContainerRef),
AIContentPromptExtension(this.viewContainerRef),
AIImagePromptExtension(this.viewContainerRef),
AIContentActionsExtension(this.viewContainerRef),
DotFloatingButton(this.injector, this.viewContainerRef),
DotTableCellExtension(this.viewContainerRef),
BubbleAssetFormExtension(this.viewContainerRef),
Expand All @@ -453,6 +448,16 @@ export class DotBlockEditorComponent implements OnInit, OnDestroy, ControlValueA
CharacterCount,
AssetUploader(this.injector, this.viewContainerRef)
];

if (this.isAIPluginInstalled) {
extensions.push(
AIContentPromptExtension(this.viewContainerRef),
AIImagePromptExtension(this.viewContainerRef),
AIContentActionsExtension(this.viewContainerRef)
);
}

return extensions;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,22 +14,25 @@ import Suggestion, { SuggestionOptions, SuggestionProps } from '@tiptap/suggesti
import { RemoteCustomExtensions } from '@dotcms/dotcms-models';

import {
SuggestionPopperModifiers,
SuggestionsCommandProps,
SuggestionsComponent,
FloatingActionsProps,
FLOATING_ACTIONS_MENU_KEYBOARD,
clearFilter,
CONTENT_SUGGESTION_ID,
ItemsType,
DotMenuItem,
findParentNode,
FLOATING_ACTIONS_MENU_KEYBOARD,
FloatingActionsKeydownProps,
FloatingActionsPlugin,
findParentNode,
DotMenuItem,
FloatingActionsProps,
ItemsType,
suggestionOptions,
clearFilter
SuggestionPopperModifiers,
SuggestionsCommandProps,
SuggestionsComponent
} from '../../shared';
import { AI_CONTENT_PROMPT_EXTENSION_NAME } from '../ai-content-prompt/ai-content-prompt.extension';
import { AI_IMAGE_PROMPT_EXTENSION_NAME } from '../ai-image-prompt/ai-image-prompt.extension';
import { NodeTypes } from '../bubble-menu/models';

const AI_BLOCK_EXTENSIONS_IDS = [AI_CONTENT_PROMPT_EXTENSION_NAME, AI_IMAGE_PROMPT_EXTENSION_NAME];
declare module '@tiptap/core' {
interface Commands<ReturnType> {
actionsMenu: {
Expand Down Expand Up @@ -210,7 +213,8 @@ function getCustomActions(customBlocks): Array<DotMenuItem> {

export const ActionsMenu = (
viewContainerRef: ViewContainerRef,
customBlocks: RemoteCustomExtensions
customBlocks: RemoteCustomExtensions,
disabledExtensions: { shouldShowAIExtensions: boolean | unknown }
) => {
let myTippy;
let suggestionsComponent: ComponentRef<SuggestionsComponent>;
Expand Down Expand Up @@ -264,6 +268,7 @@ export const ActionsMenu = (
function setUpSuggestionComponent(editor: Editor, range: Range) {
const { allowedBlocks, allowedContentTypes, lang, contentletIdentifier } =
editor.storage.dotConfig;

const editorAllowedBlocks = allowedBlocks.length > 1 ? allowedBlocks : [];
const items = getItems({ allowedBlocks: editorAllowedBlocks, editor, range });

Expand All @@ -289,10 +294,27 @@ export const ActionsMenu = (
}
}

/**
* Retrieves the items for the given parameters.
*
* @param {object} options - The options for retrieving the items.
* @param {string[]} options.allowedBlocks - The array of allowed block IDs.
* @param {object} options.editor - The editor object.
* @param {object} options.range - The range object.
* @return {DotMenuItem[]} - The array of DotMenuItem objects.
*/
function getItems({ allowedBlocks = [], editor, range }): DotMenuItem[] {
let filteredSuggestionOptions: DotMenuItem[] = [...suggestionOptions];

if (!disabledExtensions?.shouldShowAIExtensions) {
filteredSuggestionOptions = suggestionOptions.filter(
(item) => !AI_BLOCK_EXTENSIONS_IDS.includes(item.id)
);
}

const items = allowedBlocks.length
? suggestionOptions.filter((item) => allowedBlocks.includes(item.id))
: suggestionOptions;
? filteredSuggestionOptions.filter((item) => allowedBlocks.includes(item.id))
: filteredSuggestionOptions;

const customItems = [...items, ...getCustomActions(customBlocks)];

Expand Down
Loading

0 comments on commit 2bfe62e

Please sign in to comment.