1- import type { Options } from '@diplodoc/transform' ;
2- // importing only type, because lowlight and highlight.js is optional deps
3- import type HLJS from 'highlight.js/lib/core' ;
4- import type { createLowlight } from 'lowlight' with { 'resolution-mode' : 'import' } ;
51import type { Node } from 'prosemirror-model' ;
62import { Plugin , PluginKey } from 'prosemirror-state' ;
73// @ts -ignore // TODO: fix cjs build
84import { findChildrenByType } from 'prosemirror-utils' ;
9- import { Decoration , DecorationSet , type EditorView } from 'prosemirror-view' ;
5+ import { Decoration , DecorationSet } from 'prosemirror-view' ;
6+
7+ import type { ExtensionAuto } from '#core' ;
108
11- import type { ExtensionAuto } from '../../../../core' ;
12- import { capitalize } from '../../../../lodash' ;
13- import { globalLogger } from '../../../../logger' ;
149import {
1510 CodeBlockNodeAttr ,
1611 type LineNumbersOptions ,
@@ -20,22 +15,21 @@ import {
2015
2116import { CodeBlockNodeView } from './CodeBlockNodeView' ;
2217import { codeLangSelectTooltipViewCreator } from './TooltipPlugin' ;
23- import { PlainTextLang } from './const' ;
18+ import {
19+ type HighlightLangMap ,
20+ type LLRoot ,
21+ type Lowlight ,
22+ codeBlockLangsPlugin ,
23+ codeBlockLangsPluginKey ,
24+ getCodeBlockLangsState ,
25+ } from './plugins/codeBlockLangsPlugin' ;
2426import { codeBlockLineNumbersPlugin } from './plugins/codeBlockLineNumbersPlugin' ;
2527import { codeBlockLineWrappingPlugin } from './plugins/codeBlockLineWrappingPlugin' ;
2628import { processChangedCodeBlocks } from './utils' ;
2729
2830import './CodeBlockHighlight.scss' ;
2931
30- export type HighlightLangMap = Options [ 'highlightLangs' ] ;
31-
32- type Lowlight = ReturnType < typeof createLowlight > ;
33- type Root = ReturnType < Lowlight [ 'highlight' ] > ;
34-
35- type LangSelectItem = {
36- value : string ;
37- content : string ;
38- } ;
32+ export type { HighlightLangMap } ;
3933
4034const pluginKey = new PluginKey < PluginState > ( 'code_block_highlight' ) ;
4135
@@ -58,85 +52,34 @@ export type CodeBlockHighlightOptions = {
5852} ;
5953
6054export const CodeBlockHighlight : ExtensionAuto < CodeBlockHighlightOptions > = ( builder , opts ) => {
61- let langs : NonNullable < HighlightLangMap > ;
62- let lowlight : Lowlight ;
63- let hljs : typeof HLJS ;
64-
65- const loadModules = async ( ) => {
66- try {
67- hljs = ( await import ( 'highlight.js/lib/core' ) ) . default ;
68- const low = await import ( 'lowlight' ) ;
69-
70- const all : HighlightLangMap = low . all ;
71- const create : typeof createLowlight = low . createLowlight ;
72- langs = { ...all , ...opts . langs } ;
73- lowlight = create ( langs ) ;
74- return true ;
75- } catch ( e ) {
76- globalLogger . info ( 'Skip code_block highlighting' ) ;
77- builder . logger . log ( 'Skip code_block highlighting' ) ;
78- return false ;
79- }
80- } ;
81-
8255 if ( opts . lineWrapping ?. enabled ) builder . addPlugin ( codeBlockLineWrappingPlugin ) ;
8356 if ( opts . lineNumbers ?. enabled ) builder . addPlugin ( codeBlockLineNumbersPlugin ) ;
8457
85- builder . addPlugin ( ( ) => {
86- let modulesLoaded = false ;
87- let view : EditorView | null = null ;
88-
89- // empty array by default, but is filled after loading modules
90- const selectItems : LangSelectItem [ ] = [ ] ;
91- const mapping : Record < string , string > = { } ;
58+ builder . addPlugin ( ( ) => codeBlockLangsPlugin ( opts . langs , builder . logger ) ) ;
9259
60+ builder . addPlugin ( ( ) => {
9361 // TODO: add TAB key handler
9462 // TODO: Remove constant selection of block
9563 return new Plugin < PluginState > ( {
9664 key : pluginKey ,
9765 state : {
98- init : ( _ , state ) => {
99- loadModules ( ) . then ( ( loaded ) => {
100- modulesLoaded = loaded ;
101-
102- if ( modulesLoaded ) {
103- for ( const lang of Object . keys ( langs ) ) {
104- const defs = langs [ lang ] ( hljs ) ;
105- selectItems . push ( {
106- value : lang ,
107- content : defs . name || capitalize ( lang ) ,
108- } ) ;
109- if ( defs . aliases ) {
110- for ( const alias of defs . aliases ) {
111- mapping [ alias ] = lang ;
112- }
113- }
114- }
115-
116- selectItems . sort ( sortLangs ) ;
117-
118- if ( view && ! view . isDestroyed ) {
119- view . dispatch ( view . state . tr . setMeta ( pluginKey , { modulesLoaded} ) ) ;
120- }
121- }
122- } ) ;
123-
66+ init : ( _config , _state ) => {
12467 const cache : HighlightCache = new WeakMap ( ) ;
125-
126- return {
127- cache,
128- decoSet : modulesLoaded
129- ? DecorationSet . empty
130- : getDecorations ( state . doc , cache ) ,
131- } ;
68+ return { cache, decoSet : DecorationSet . empty } ;
13269 } ,
133- apply : ( tr , { cache, decoSet} ) => {
134- if ( ! modulesLoaded ) {
135- return { cache, decoSet : DecorationSet . empty } ;
70+ apply : ( tr , { cache, decoSet} , _oldState , newState ) => {
71+ const langsUpdate = tr . getMeta ( codeBlockLangsPluginKey ) ;
72+ if ( langsUpdate ?. loaded && langsUpdate . lowlight ) {
73+ return {
74+ cache,
75+ decoSet : getDecorations ( tr . doc , cache , langsUpdate . lowlight ) ,
76+ } ;
13677 }
13778
138- if ( tr . getMeta ( pluginKey ) ?. modulesLoaded ) {
139- return { cache, decoSet : getDecorations ( tr . doc , cache ) } ;
79+ const { lowlight} = getCodeBlockLangsState ( newState ) ;
80+
81+ if ( ! lowlight ) {
82+ return { cache, decoSet : DecorationSet . empty } ;
14083 }
14184
14285 if ( ! tr . docChanged ) return { cache, decoSet} ;
@@ -167,9 +110,8 @@ export const CodeBlockHighlight: ExtensionAuto<CodeBlockHighlightOptions> = (bui
167110 return { cache, decoSet} ;
168111 } ,
169112 } ,
170- view : ( v ) => {
171- view = v ;
172- return codeLangSelectTooltipViewCreator ( view , selectItems , mapping , {
113+ view : ( view ) => {
114+ return codeLangSelectTooltipViewCreator ( view , {
173115 showCodeWrapping : Boolean ( opts . lineWrapping ?. enabled ) ,
174116 showLineNumbers : Boolean ( opts . lineNumbers ?. enabled ) ,
175117 } ) ;
@@ -185,11 +127,7 @@ export const CodeBlockHighlight: ExtensionAuto<CodeBlockHighlightOptions> = (bui
185127 } ) ;
186128 } ) ;
187129
188- function getDecorations ( doc : Node , cache : HighlightCache ) {
189- if ( ! lowlight ) {
190- return DecorationSet . empty ;
191- }
192-
130+ function getDecorations ( doc : Node , cache : HighlightCache , lowlight : Lowlight ) {
193131 const decos : Decoration [ ] = [ ] ;
194132
195133 for ( const { node, pos} of findChildrenByType ( doc , codeBlockType ( doc . type . schema ) , true ) ) {
@@ -233,7 +171,7 @@ function renderTree(parsedNodes: HighlightParsedTree, from: number): Decoration[
233171}
234172
235173function parseNodes (
236- nodes : Root [ 'children' ] ,
174+ nodes : LLRoot [ 'children' ] ,
237175 className : readonly string [ ] = [ ] ,
238176) : HighlightParsedTree {
239177 const result : HighlightParsedTree = [ ] ;
@@ -242,7 +180,7 @@ function parseNodes(
242180}
243181
244182function collectNodes (
245- nodes : Root [ 'children' ] ,
183+ nodes : LLRoot [ 'children' ] ,
246184 className : readonly string [ ] ,
247185 result : HighlightParsedTree ,
248186) : void {
@@ -258,10 +196,3 @@ function collectNodes(
258196 }
259197 }
260198}
261-
262- function sortLangs ( a : LangSelectItem , b : LangSelectItem ) : number {
263- // plaintext always goes first
264- if ( a . value === PlainTextLang ) return - 1 ;
265- if ( b . value === PlainTextLang ) return 1 ;
266- return 0 ;
267- }
0 commit comments