From 21ec90dd43428e9d8bc5263a0e605a20d3794384 Mon Sep 17 00:00:00 2001 From: Andreas Fernandez <a.fernandez@scripting-base.de> Date: Sat, 29 Feb 2020 12:40:36 +0100 Subject: [PATCH] [BUGFIX] Initialize CodeMirror if element becomes visible to client MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CodeMirror initialization fails if its textarea is not rendered yet. To fix this issue, an Intersection Observer is installed and initializes the CodeMirror instance when the element becomes visibile to the client. Resolves: #90525 Releases: master, 9.5 Change-Id: I93247a41827547abec5369e5904ec4dd2354eb7e Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/63504 Tested-by: TYPO3com <noreply@typo3.com> Tested-by: Christian Eßl <indy.essl@gmail.com> Tested-by: Riccardo De Contardi <erredeco@gmail.com> Tested-by: Daniel Goerz <daniel.goerz@posteo.de> Reviewed-by: Christian Eßl <indy.essl@gmail.com> Reviewed-by: Daniel Goerz <daniel.goerz@posteo.de> --- .../Resources/Public/TypeScript/T3editor.ts | 131 ++++++++++-------- .../Classes/Form/Element/T3editorElement.php | 2 +- .../Resources/Public/JavaScript/T3editor.js | 2 +- 3 files changed, 75 insertions(+), 60 deletions(-) diff --git a/Build/Sources/TypeScript/t3editor/Resources/Public/TypeScript/T3editor.ts b/Build/Sources/TypeScript/t3editor/Resources/Public/TypeScript/T3editor.ts index 19c616b0278f..c7d61962eb07 100644 --- a/Build/Sources/TypeScript/t3editor/Resources/Public/TypeScript/T3editor.ts +++ b/Build/Sources/TypeScript/t3editor/Resources/Public/TypeScript/T3editor.ts @@ -46,72 +46,87 @@ class T3editor { } /** - * Initializes CodeMirror on available texteditors + * Initialize the events */ - public findAndInitializeEditors(): void { - $(document).find('textarea.t3editor').each(function(this: Element): void { - const $textarea = $(this); - - if (!$textarea.prop('is_t3editor')) { - const config = $textarea.data('codemirror-config'); - const modeParts = config.mode.split('/'); - const addons = $.merge([modeParts.join('/')], JSON.parse(config.addons)); - const options = JSON.parse(config.options); - - // load mode + registered addons - require(addons, (): void => { - const cm = CodeMirror.fromTextArea($textarea.get(0), { - extraKeys: { - 'Ctrl-F': 'findPersistent', - 'Cmd-F': 'findPersistent', - 'Ctrl-Alt-F': (codemirror: any): void => { - codemirror.setOption('fullScreen', !codemirror.getOption('fullScreen')); - }, - 'Ctrl-Space': 'autocomplete', - 'Esc': (codemirror: any): void => { - if (codemirror.getOption('fullScreen')) { - codemirror.setOption('fullScreen', false); - } - }, - }, - fullScreen: false, - lineNumbers: true, - lineWrapping: true, - mode: modeParts[modeParts.length - 1], - }); - - // set options - $.each(options, (key: string, value: any): void => { - cm.setOption(key, value); - }); - - // Mark form as changed if code editor content has changed - cm.on('change', (): void => { - FormEngine.Validation.markFieldAsChanged($textarea); - }); - - cm.addPanel( - T3editor.createPanelNode('bottom', $textarea.attr('alt')), - { - position: 'bottom', - stable: true, - }, - ); - }); - - $textarea.prop('is_t3editor', true); - } + public initialize(): void { + $((): void => { + this.observeEditorCandidates(); }); } /** - * Initialize the events + * Initializes CodeMirror on available texteditors */ - public initialize(): void { - $((): void => { - this.findAndInitializeEditors(); + public observeEditorCandidates(): void { + const observerOptions = { + root: document.body + }; + + let observer = new IntersectionObserver((entries: IntersectionObserverEntry[]): void => { + entries.forEach((entry: IntersectionObserverEntry): void => { + if (entry.intersectionRatio > 0) { + const $target = $(entry.target); + if (!$target.prop('is_t3editor')) { + this.initializeEditor($target); + } + } + }) + }, observerOptions); + + document.querySelectorAll('textarea.t3editor').forEach((textarea: HTMLTextAreaElement): void => { + observer.observe(textarea); }); } + + private initializeEditor($textarea: JQuery): void { + const config = $textarea.data('codemirror-config'); + const modeParts = config.mode.split('/'); + const addons = $.merge([modeParts.join('/')], JSON.parse(config.addons)); + const options = JSON.parse(config.options); + + // load mode + registered addons + require(addons, (): void => { + const cm = CodeMirror.fromTextArea($textarea.get(0), { + extraKeys: { + 'Ctrl-F': 'findPersistent', + 'Cmd-F': 'findPersistent', + 'Ctrl-Alt-F': (codemirror: any): void => { + codemirror.setOption('fullScreen', !codemirror.getOption('fullScreen')); + }, + 'Ctrl-Space': 'autocomplete', + 'Esc': (codemirror: any): void => { + if (codemirror.getOption('fullScreen')) { + codemirror.setOption('fullScreen', false); + } + }, + }, + fullScreen: false, + lineNumbers: true, + lineWrapping: true, + mode: modeParts[modeParts.length - 1], + }); + + // set options + $.each(options, (key: string, value: any): void => { + cm.setOption(key, value); + }); + + // Mark form as changed if code editor content has changed + cm.on('change', (): void => { + FormEngine.Validation.markFieldAsChanged($textarea); + }); + + cm.addPanel( + T3editor.createPanelNode('bottom', $textarea.attr('alt')), + { + position: 'bottom', + stable: true, + }, + ); + }); + + $textarea.prop('is_t3editor', true); + } } // create an instance and return it diff --git a/typo3/sysext/t3editor/Classes/Form/Element/T3editorElement.php b/typo3/sysext/t3editor/Classes/Form/Element/T3editorElement.php index 8f23804b2835..9462abcc9dbd 100644 --- a/typo3/sysext/t3editor/Classes/Form/Element/T3editorElement.php +++ b/typo3/sysext/t3editor/Classes/Form/Element/T3editorElement.php @@ -100,7 +100,7 @@ class T3editorElement extends AbstractFormElement $this->resultArray['stylesheetFiles'][] = $codeMirrorPath . '/lib/codemirror.css'; $this->resultArray['stylesheetFiles'][] = $this->extPath . '/Resources/Public/Css/t3editor.css'; $this->resultArray['requireJsModules'][] = [ - 'TYPO3/CMS/T3editor/T3editor' => 'function(T3editor) {T3editor.findAndInitializeEditors()}' + 'TYPO3/CMS/T3editor/T3editor' => 'function(T3editor) {T3editor.observeEditorCandidates()}' ]; // Compile and register t3editor configuration diff --git a/typo3/sysext/t3editor/Resources/Public/JavaScript/T3editor.js b/typo3/sysext/t3editor/Resources/Public/JavaScript/T3editor.js index 109676b53979..61e0438d0a59 100644 --- a/typo3/sysext/t3editor/Resources/Public/JavaScript/T3editor.js +++ b/typo3/sysext/t3editor/Resources/Public/JavaScript/T3editor.js @@ -10,4 +10,4 @@ * * The TYPO3 project - inspiring people to share! */ -define(["require","exports","cm/lib/codemirror","jquery","TYPO3/CMS/Backend/FormEngine"],(function(e,t,i,n,r){"use strict";class o{static createPanelNode(e,t){return n("<div />",{class:"CodeMirror-panel CodeMirror-panel-"+e,id:"panel-"+e}).append(n("<span />").text(t)).get(0)}constructor(){this.initialize()}findAndInitializeEditors(){n(document).find("textarea.t3editor").each((function(){const t=n(this);if(!t.prop("is_t3editor")){const a=t.data("codemirror-config"),s=a.mode.split("/"),l=n.merge([s.join("/")],JSON.parse(a.addons)),d=JSON.parse(a.options);e(l,()=>{const e=i.fromTextArea(t.get(0),{extraKeys:{"Ctrl-F":"findPersistent","Cmd-F":"findPersistent","Ctrl-Alt-F":e=>{e.setOption("fullScreen",!e.getOption("fullScreen"))},"Ctrl-Space":"autocomplete",Esc:e=>{e.getOption("fullScreen")&&e.setOption("fullScreen",!1)}},fullScreen:!1,lineNumbers:!0,lineWrapping:!0,mode:s[s.length-1]});n.each(d,(t,i)=>{e.setOption(t,i)}),e.on("change",()=>{r.Validation.markFieldAsChanged(t)}),e.addPanel(o.createPanelNode("bottom",t.attr("alt")),{position:"bottom",stable:!0})}),t.prop("is_t3editor",!0)}}))}initialize(){n(()=>{this.findAndInitializeEditors()})}}return new o})); \ No newline at end of file +define(["require","exports","cm/lib/codemirror","jquery","TYPO3/CMS/Backend/FormEngine"],(function(e,t,i,r,o){"use strict";class n{static createPanelNode(e,t){return r("<div />",{class:"CodeMirror-panel CodeMirror-panel-"+e,id:"panel-"+e}).append(r("<span />").text(t)).get(0)}constructor(){this.initialize()}initialize(){r(()=>{this.observeEditorCandidates()})}observeEditorCandidates(){const e={root:document.body};let t=new IntersectionObserver(e=>{e.forEach(e=>{if(e.intersectionRatio>0){const t=r(e.target);t.prop("is_t3editor")||this.initializeEditor(t)}})},e);document.querySelectorAll("textarea.t3editor").forEach(e=>{t.observe(e)})}initializeEditor(t){const a=t.data("codemirror-config"),s=a.mode.split("/"),l=r.merge([s.join("/")],JSON.parse(a.addons)),d=JSON.parse(a.options);e(l,()=>{const e=i.fromTextArea(t.get(0),{extraKeys:{"Ctrl-F":"findPersistent","Cmd-F":"findPersistent","Ctrl-Alt-F":e=>{e.setOption("fullScreen",!e.getOption("fullScreen"))},"Ctrl-Space":"autocomplete",Esc:e=>{e.getOption("fullScreen")&&e.setOption("fullScreen",!1)}},fullScreen:!1,lineNumbers:!0,lineWrapping:!0,mode:s[s.length-1]});r.each(d,(t,i)=>{e.setOption(t,i)}),e.on("change",()=>{o.Validation.markFieldAsChanged(t)}),e.addPanel(n.createPanelNode("bottom",t.attr("alt")),{position:"bottom",stable:!0})}),t.prop("is_t3editor",!0)}}return new n})); \ No newline at end of file -- GitLab