@@ -4,12 +4,91 @@ import { SidebarPortal } from '@plone/volto/components';
44import config from '@plone/volto/registry' ;
55import CodeBlockData from './Data' ;
66import Caption from '../../Caption/Caption.jsx' ;
7- import Editor from '../../Editor/Editor.tsx' ;
8- import { highlight } from 'prismjs/components/prism-core' ;
7+ import hljs from 'highlight.js/lib/core' ;
8+ import javascript from 'highlight.js/lib/languages/javascript' ;
9+ import python from 'highlight.js/lib/languages/python' ;
10+ import xml from 'highlight.js/lib/languages/xml' ;
11+ import css from 'highlight.js/lib/languages/css' ;
12+ import json from 'highlight.js/lib/languages/json' ;
13+ import bash from 'highlight.js/lib/languages/bash' ;
14+ import c from 'highlight.js/lib/languages/c' ;
15+ import cpp from 'highlight.js/lib/languages/cpp' ;
16+ import csharp from 'highlight.js/lib/languages/csharp' ;
17+ import diff from 'highlight.js/lib/languages/diff' ;
18+ import go from 'highlight.js/lib/languages/go' ;
19+ import java from 'highlight.js/lib/languages/java' ;
20+ import kotlin from 'highlight.js/lib/languages/kotlin' ;
21+ import less from 'highlight.js/lib/languages/less' ;
22+ import lua from 'highlight.js/lib/languages/lua' ;
23+ import makefile from 'highlight.js/lib/languages/makefile' ;
24+ import markdown from 'highlight.js/lib/languages/markdown' ;
25+ import objectivec from 'highlight.js/lib/languages/objectivec' ;
26+ import perl from 'highlight.js/lib/languages/perl' ;
27+ import php from 'highlight.js/lib/languages/php' ;
28+ import ruby from 'highlight.js/lib/languages/ruby' ;
29+ import rust from 'highlight.js/lib/languages/rust' ;
30+ import scss from 'highlight.js/lib/languages/scss' ;
31+ import sql from 'highlight.js/lib/languages/sql' ;
32+ import swift from 'highlight.js/lib/languages/swift' ;
33+ import typescript from 'highlight.js/lib/languages/typescript' ;
34+ import yaml from 'highlight.js/lib/languages/yaml' ;
35+ import dockerfile from 'highlight.js/lib/languages/dockerfile' ;
36+ import nginx from 'highlight.js/lib/languages/nginx' ;
37+ import powershell from 'highlight.js/lib/languages/powershell' ;
38+ import r from 'highlight.js/lib/languages/r' ;
39+ import 'highlight.js/styles/github.css' ;
40+
41+ // Register languages
42+ hljs . registerLanguage ( 'javascript' , javascript ) ;
43+ hljs . registerLanguage ( 'python' , python ) ;
44+ hljs . registerLanguage ( 'xml' , xml ) ;
45+ hljs . registerLanguage ( 'html' , xml ) ;
46+ hljs . registerLanguage ( 'css' , css ) ;
47+ hljs . registerLanguage ( 'json' , json ) ;
48+ hljs . registerLanguage ( 'bash' , bash ) ;
49+ hljs . registerLanguage ( 'shell' , bash ) ;
50+ hljs . registerLanguage ( 'c' , c ) ;
51+ hljs . registerLanguage ( 'cpp' , cpp ) ;
52+ hljs . registerLanguage ( 'csharp' , csharp ) ;
53+ hljs . registerLanguage ( 'diff' , diff ) ;
54+ hljs . registerLanguage ( 'go' , go ) ;
55+ hljs . registerLanguage ( 'java' , java ) ;
56+ hljs . registerLanguage ( 'kotlin' , kotlin ) ;
57+ hljs . registerLanguage ( 'less' , less ) ;
58+ hljs . registerLanguage ( 'lua' , lua ) ;
59+ hljs . registerLanguage ( 'makefile' , makefile ) ;
60+ hljs . registerLanguage ( 'markdown' , markdown ) ;
61+ hljs . registerLanguage ( 'objectivec' , objectivec ) ;
62+ hljs . registerLanguage ( 'perl' , perl ) ;
63+ hljs . registerLanguage ( 'php' , php ) ;
64+ hljs . registerLanguage ( 'ruby' , ruby ) ;
65+ hljs . registerLanguage ( 'rust' , rust ) ;
66+ hljs . registerLanguage ( 'scss' , scss ) ;
67+ hljs . registerLanguage ( 'sql' , sql ) ;
68+ hljs . registerLanguage ( 'swift' , swift ) ;
69+ hljs . registerLanguage ( 'typescript' , typescript ) ;
70+ hljs . registerLanguage ( 'yaml' , yaml ) ;
71+ hljs . registerLanguage ( 'dockerfile' , dockerfile ) ;
72+ hljs . registerLanguage ( 'nginx' , nginx ) ;
73+ hljs . registerLanguage ( 'powershell' , powershell ) ;
74+ hljs . registerLanguage ( 'r' , r ) ;
75+
76+ // Register aliases
77+ hljs . registerLanguage ( 'jsx' , javascript ) ;
78+ hljs . registerLanguage ( 'tsx' , typescript ) ;
79+ hljs . registerLanguage ( 'text' , ( ) => ( { } ) ) ; // Plain text
80+ hljs . registerLanguage ( 'plain' , ( ) => ( { } ) ) ; // Plain text for backwards compatibility
81+ hljs . registerLanguage ( 'mermaid' , ( ) => ( { } ) ) ; // Mermaid as plain text for backwards compatibility
82+ hljs . registerLanguage ( 'docker' , dockerfile ) ;
83+ hljs . registerLanguage ( 'batch' , powershell ) ;
84+ hljs . registerLanguage ( 'fish' , bash ) ;
85+ hljs . registerLanguage ( 'zsh' , bash ) ;
986
1087const CodeBlockEdit = ( props ) => {
1188 const { data, selected, block, onChangeBlock } = props ;
1289 const [ code , setCode ] = React . useState ( data . code || '' ) ;
90+ const codeRef = React . useRef ( null ) ;
91+ const cursorPositionRef = React . useRef ( 0 ) ;
1392 const className = `code-block-wrapper edit ${ data . style } ` ;
1493 const codeBlockConfig = config . blocks ?. blocksConfig ?. codeBlock ;
1594 const defaultLanguage = codeBlockConfig ?. defaultLanguage ;
@@ -19,23 +98,168 @@ const CodeBlockEdit = (props) => {
1998 data . style = defaultStyle ;
2099 }
21100 const allLanguages = config . settings . codeBlock . languages ;
22- const language = allLanguages [ data . language ] . language ;
101+ const language =
102+ data . language && allLanguages [ data . language ]
103+ ? data . language
104+ : defaultLanguage || 'javascript' ;
105+
106+ // Create highlight function using highlight.js
107+ const highlightCode = ( code ) => {
108+ if ( ! code ) return code ;
109+ try {
110+ const result = hljs . highlight ( code , { language : language } ) ;
111+ return result . value ;
112+ } catch ( err ) {
113+ // Fallback to auto-detection or plain text
114+ try {
115+ const result = hljs . highlightAuto ( code ) ;
116+ return result . value ;
117+ } catch ( err2 ) {
118+ // Return code with basic escaping
119+ return code
120+ . replace ( / & / g, '&' )
121+ . replace ( / < / g, '<' )
122+ . replace ( / > / g, '>' ) ;
123+ }
124+ }
125+ } ;
126+
127+ const processCodeWithLineNumbers = ( code ) => {
128+ if ( ! data . showLineNumbers ) {
129+ return highlightCode ( code ) ;
130+ }
131+
132+ const lines = code . split ( '\n' ) ;
133+ // Remove only the last empty line if it exists
134+ if ( lines . length > 1 && lines [ lines . length - 1 ] === '' ) {
135+ lines . pop ( ) ;
136+ }
137+ const startNum = parseInt ( data . lineNbr , 10 ) || 1 ;
138+
139+ // Process each line individually and add line numbers
140+ const processedLines = lines . map ( ( line , index ) => {
141+ try {
142+ const result = hljs . highlight ( line , { language : language } ) ;
143+ return `<span class="code-line" data-line="${ startNum + index } ">${ result . value } </span>` ;
144+ } catch ( err ) {
145+ try {
146+ const result = hljs . highlightAuto ( line ) ;
147+ return `<span class="code-line" data-line="${ startNum + index } ">${ result . value } </span>` ;
148+ } catch ( err2 ) {
149+ return `<span class="code-line" data-line="${ startNum + index } ">${ line } </span>` ;
150+ }
151+ }
152+ } ) ;
153+
154+ return processedLines . join ( '\n' ) ;
155+ } ;
156+
23157 const { caption_title, caption_description } = data ;
24- const handleChange = ( code ) => {
25- setCode ( code ) ;
26- onChangeBlock ( block , { ...data , code : code } ) ;
158+
159+ // Save and restore cursor position
160+ const saveCursorPosition = ( ) => {
161+ const selection = window . getSelection ( ) ;
162+ if ( selection . rangeCount > 0 ) {
163+ const range = selection . getRangeAt ( 0 ) ;
164+ const preCaretRange = range . cloneRange ( ) ;
165+ preCaretRange . selectNodeContents ( codeRef . current ) ;
166+ preCaretRange . setEnd ( range . endContainer , range . endOffset ) ;
167+ return preCaretRange . toString ( ) . length ;
168+ }
169+ return 0 ;
27170 } ;
28171
172+ const restoreCursorPosition = ( position ) => {
173+ const selection = window . getSelection ( ) ;
174+ const range = document . createRange ( ) ;
175+ let charCount = 0 ;
176+ let nodeStack = [ codeRef . current ] ;
177+ let node ;
178+ let foundStart = false ;
179+
180+ while ( ! foundStart && ( node = nodeStack . pop ( ) ) ) {
181+ if ( node . nodeType === Node . TEXT_NODE ) {
182+ const nextCharCount = charCount + node . textContent . length ;
183+ if ( position >= charCount && position <= nextCharCount ) {
184+ range . setStart ( node , position - charCount ) ;
185+ range . setEnd ( node , position - charCount ) ;
186+ foundStart = true ;
187+ }
188+ charCount = nextCharCount ;
189+ } else {
190+ for ( let i = node . childNodes . length - 1 ; i >= 0 ; i -- ) {
191+ nodeStack . push ( node . childNodes [ i ] ) ;
192+ }
193+ }
194+ }
195+
196+ if ( foundStart ) {
197+ selection . removeAllRanges ( ) ;
198+ selection . addRange ( range ) ;
199+ }
200+ } ;
201+
202+ const handleChange = ( newCode ) => {
203+ const cursorPosition = saveCursorPosition ( ) ;
204+ cursorPositionRef . current = cursorPosition ;
205+ setCode ( newCode ) ;
206+ onChangeBlock ( block , { ...data , code : newCode } ) ;
207+ } ;
208+
209+ // Restore cursor position after re-render
210+ React . useLayoutEffect ( ( ) => {
211+ if ( cursorPositionRef . current > 0 ) {
212+ restoreCursorPosition ( cursorPositionRef . current ) ;
213+ cursorPositionRef . current = 0 ;
214+ }
215+ } , [ code ] ) ;
216+
217+
29218 return (
30219 < div className = "block code" >
31220 < div className = { className } >
32- < Editor
33- value = { code }
34- onValueChange = { ( code ) => handleChange ( code ) }
35- highlight = { ( code ) => highlight ( code , language ) }
36- padding = { 10 }
37- preClassName = { `code-block-wrapper ${ data . style } language-${ data . language } ` }
38- />
221+ < div className = { `code-editor-container ${ data . showLineNumbers ? 'with-line-numbers' : '' } ` } >
222+ < pre className = { `code-editor-with-highlighting language-${ language } ` } >
223+ < code
224+ ref = { codeRef }
225+ className = { `language-${ language } ` }
226+ contentEditable
227+ suppressContentEditableWarning = { true }
228+ onInput = { ( e ) => handleChange ( e . target . textContent ) }
229+ onKeyDown = { ( e ) => {
230+ if ( e . key === 'Enter' ) {
231+ e . preventDefault ( ) ;
232+ const selection = window . getSelection ( ) ;
233+ if ( selection . rangeCount > 0 ) {
234+ const range = selection . getRangeAt ( 0 ) ;
235+ const textNode = document . createTextNode ( '\n' ) ;
236+ range . insertNode ( textNode ) ;
237+ range . setStartAfter ( textNode ) ;
238+ range . setEndAfter ( textNode ) ;
239+ selection . removeAllRanges ( ) ;
240+ selection . addRange ( range ) ;
241+ handleChange ( e . target . textContent ) ;
242+ }
243+ }
244+ } }
245+ style = { {
246+ fontFamily :
247+ 'Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace' ,
248+ fontSize : '14px' ,
249+ lineHeight : '1.4' ,
250+ outline : 'none' ,
251+ whiteSpace : data . wrapLongLines ? 'pre-wrap' : 'pre' ,
252+ minHeight : '200px' ,
253+ display : 'block' ,
254+ padding : data . showLineNumbers ? '15px 15px 15px 60px' : '15px' ,
255+ margin : 0 ,
256+ border : 0 ,
257+ } }
258+ dangerouslySetInnerHTML = { { __html : data . showLineNumbers ? processCodeWithLineNumbers ( code ) : highlightCode ( code ) } }
259+ />
260+ </ pre >
261+ </ div >
262+
39263 { caption_title && (
40264 < Caption title = { caption_title } description = { caption_description } />
41265 ) }
0 commit comments