diff --git a/packages/ckeditor5/src/plugins.ts b/packages/ckeditor5/src/plugins.ts index c53dfb924..29abde14c 100644 --- a/packages/ckeditor5/src/plugins.ts +++ b/packages/ckeditor5/src/plugins.ts @@ -24,6 +24,7 @@ import "@triliumnext/ckeditor5-admonition/index.css"; import "@triliumnext/ckeditor5-footnotes/index.css"; import "@triliumnext/ckeditor5-math/index.css"; import CodeBlockToolbar from "./plugins/code_block_toolbar.js"; +import CodeBlockLanguageDropdown from "./plugins/code_block_language_dropdown.js"; /** * Plugins that are specific to Trilium and not part of the CKEditor 5 core, included in both text editors but not in the attribute editor. @@ -40,6 +41,7 @@ const TRILIUM_PLUGINS: typeof Plugin[] = [ IncludeNote, Uploadfileplugin, SyntaxHighlighting, + CodeBlockLanguageDropdown, CodeBlockToolbar ]; diff --git a/packages/ckeditor5/src/plugins/code_block_language_dropdown.ts b/packages/ckeditor5/src/plugins/code_block_language_dropdown.ts new file mode 100644 index 000000000..7b384a784 --- /dev/null +++ b/packages/ckeditor5/src/plugins/code_block_language_dropdown.ts @@ -0,0 +1,103 @@ +import { Editor, CodeBlock, Plugin, type ListDropdownButtonDefinition, Collection, type CodeBlockCommand, ViewModel, createDropdown, addListToDropdown, DropdownButtonView } from "ckeditor5"; + +/** + * Toolbar item which displays the list of languages in a dropdown, with the text visible (similar to the headings switcher), as opposed to the default split button implementation. + */ +export default class CodeBlockLanguageDropdown extends Plugin { + + static get requires() { + return [ CodeBlock ]; + } + + public init() { + const editor = this.editor; + const componentFactory = editor.ui.componentFactory; + + const normalizedLanguageDefs = this._getNormalizedAndLocalizedLanguageDefinitions(editor); + const itemDefinitions = this._getLanguageListItemDefinitions(normalizedLanguageDefs); + const command: CodeBlockCommand = editor.commands.get( 'codeBlock' )!; + + componentFactory.add("codeBlockDropdown", locale => { + const dropdownView = createDropdown(this.editor.locale, DropdownButtonView); + dropdownView.buttonView.set({ + withText: true + }); + dropdownView.bind( 'isEnabled' ).to( command, 'value', value => !!value ); + dropdownView.buttonView.bind( 'label' ).to( command, 'value', (value) => { + const itemDefinition = normalizedLanguageDefs.find((def) => def.language === value); + return itemDefinition?.label; + }); + dropdownView.on( 'execute', evt => { + editor.execute( 'codeBlock', { + language: ( evt.source as any )._codeBlockLanguage, + forceValue: true + }); + + editor.editing.view.focus(); + }); + addListToDropdown(dropdownView, itemDefinitions); + return dropdownView; + }); + } + + // Adapted from packages/ckeditor5-code-block/src/codeblockui.ts + private _getLanguageListItemDefinitions( + normalizedLanguageDefs: Array + ): Collection { + const editor = this.editor; + const command: CodeBlockCommand = editor.commands.get( 'codeBlock' )!; + const itemDefinitions = new Collection(); + + for ( const languageDef of normalizedLanguageDefs ) { + const definition: ListDropdownButtonDefinition = { + type: 'button', + model: new ViewModel( { + _codeBlockLanguage: languageDef.language, + label: languageDef.label, + role: 'menuitemradio', + withText: true + } ) + }; + + definition.model.bind( 'isOn' ).to( command, 'value', value => { + return value === definition.model._codeBlockLanguage; + } ); + + itemDefinitions.add( definition ); + } + + return itemDefinitions; + } + + // Adapted from packages/ckeditor5-code-block/src/utils.ts + private _getNormalizedAndLocalizedLanguageDefinitions(editor: Editor) { + const languageDefs = editor.config.get( 'codeBlock.languages' ) as Array; + for ( const def of languageDefs ) { + if ( def.class === undefined ) { + def.class = `language-${ def.language }`; + } + } + return languageDefs; + } + +} + +interface CodeBlockLanguageDefinition { + + /** + * The name of the language that will be stored in the model attribute. Also, when `class` + * is not specified, it will be used to create the CSS class associated with the language (prefixed by "language-"). + */ + language: string; + + /** + * The human–readable label associated with the language and displayed in the UI. + */ + label: string; + + /** + * The CSS class associated with the language. When not specified the `language` + * property is used to create a class prefixed by "language-". + */ + class?: string; +} diff --git a/packages/ckeditor5/src/plugins/code_block_toolbar.ts b/packages/ckeditor5/src/plugins/code_block_toolbar.ts index 259f98bc4..8e4e1081a 100644 --- a/packages/ckeditor5/src/plugins/code_block_toolbar.ts +++ b/packages/ckeditor5/src/plugins/code_block_toolbar.ts @@ -1,40 +1,10 @@ -import { Editor, CodeBlock, Plugin, ViewDocumentFragment, WidgetToolbarRepository, type ViewNode, type ListDropdownButtonDefinition, Collection, type CodeBlockCommand, ViewModel, createDropdown, addListToDropdown, DropdownButtonView } from "ckeditor5"; +import { CodeBlock, Plugin, ViewDocumentFragment, WidgetToolbarRepository, type ViewNode } from "ckeditor5"; +import CodeBlockLanguageDropdown from "./code_block_language_dropdown"; export default class CodeBlockToolbar extends Plugin { static get requires() { - return [ WidgetToolbarRepository, CodeBlock ] as const; - } - - public init(): void { - const editor = this.editor; - const componentFactory = editor.ui.componentFactory; - - const normalizedLanguageDefs = this._getNormalizedAndLocalizedLanguageDefinitions(editor); - const itemDefinitions = this._getLanguageListItemDefinitions(normalizedLanguageDefs); - const command: CodeBlockCommand = editor.commands.get( 'codeBlock' )!; - - componentFactory.add("codeBlockDropdown", locale => { - const dropdownView = createDropdown(this.editor.locale, DropdownButtonView); - dropdownView.buttonView.set({ - withText: true - }); - dropdownView.bind( 'isEnabled' ).to( command, 'value', value => !!value ); - dropdownView.buttonView.bind( 'label' ).to( command, 'value', (value) => { - const itemDefinition = normalizedLanguageDefs.find((def) => def.language === value); - return itemDefinition?.label; - }); - dropdownView.on( 'execute', evt => { - editor.execute( 'codeBlock', { - language: ( evt.source as any )._codeBlockLanguage, - forceValue: true - }); - - editor.editing.view.focus(); - }); - addListToDropdown(dropdownView, itemDefinitions); - return dropdownView; - }); + return [ WidgetToolbarRepository, CodeBlock, CodeBlockLanguageDropdown ] as const; } afterInit() { @@ -65,64 +35,4 @@ export default class CodeBlockToolbar extends Plugin { }); } - // Adapted from packages/ckeditor5-code-block/src/codeblockui.ts - private _getLanguageListItemDefinitions( - normalizedLanguageDefs: Array - ): Collection { - const editor = this.editor; - const command: CodeBlockCommand = editor.commands.get( 'codeBlock' )!; - const itemDefinitions = new Collection(); - - for ( const languageDef of normalizedLanguageDefs ) { - const definition: ListDropdownButtonDefinition = { - type: 'button', - model: new ViewModel( { - _codeBlockLanguage: languageDef.language, - label: languageDef.label, - role: 'menuitemradio', - withText: true - } ) - }; - - definition.model.bind( 'isOn' ).to( command, 'value', value => { - return value === definition.model._codeBlockLanguage; - } ); - - itemDefinitions.add( definition ); - } - - return itemDefinitions; - } - - // Adapted from packages/ckeditor5-code-block/src/utils.ts - private _getNormalizedAndLocalizedLanguageDefinitions(editor: Editor) { - const languageDefs = editor.config.get( 'codeBlock.languages' ) as Array; - for ( const def of languageDefs ) { - if ( def.class === undefined ) { - def.class = `language-${ def.language }`; - } - } - return languageDefs; - } - -} - -interface CodeBlockLanguageDefinition { - - /** - * The name of the language that will be stored in the model attribute. Also, when `class` - * is not specified, it will be used to create the CSS class associated with the language (prefixed by "language-"). - */ - language: string; - - /** - * The human–readable label associated with the language and displayed in the UI. - */ - label: string; - - /** - * The CSS class associated with the language. When not specified the `language` - * property is used to create a class prefixed by "language-". - */ - class?: string; }