ngx-codemirror icon indicating copy to clipboard operation
ngx-codemirror copied to clipboard

Can not use some addons

Open johaven opened this issue 6 years ago • 16 comments

I try to use this addon: https://codemirror.net/demo/loadmode.html

The idea is to put all folder languages in my assets dir, and give formatted url etc ...

How can i access to this variable https://github.com/codemirror/CodeMirror/blob/29e338b8c1f27ade4502839877df7afd49584479/demo/loadmode.html#L65 with ngx-codemirror ?

johaven avatar Nov 04 '18 17:11 johaven

accessing the editor property

use a viewchild ref to access the component and then its at the public codemirror property https://github.com/TypeCtrl/ngx-codemirror/blob/master/src/lib/codemirror.component.ts#L87

scttcper avatar Nov 05 '18 16:11 scttcper

Thank you :)

johaven avatar Nov 05 '18 17:11 johaven

Another problem: CodeMirror.autoLoadMode(instance, mode) must be called but this.codeMirror.codeMirror.autoLoadMode is not a function

After reading code of loadmode.js: https://github.com/codemirror/CodeMirror/blob/29e338b8c1f27ade4502839877df7afd49584479/addon/mode/loadmode.js#L58

The function seems to not be declared properly on codeMirror instance

johaven avatar Nov 05 '18 20:11 johaven

I think is related to: https://github.com/codemirror/CodeMirror/issues/5484

Your import seems to be the same as described: https://github.com/TypeCtrl/ngx-codemirror/blob/d5cafceb528b8b95643bd0fa3a7ff858a5c63582/src/lib/codemirror.component.ts#L23 and nothing is correctly declared to the "fake" instance described.

johaven avatar Nov 05 '18 21:11 johaven

its imported down further, those are just @types being imported at the top https://github.com/TypeCtrl/ngx-codemirror/blob/d5cafceb528b8b95643bd0fa3a7ff858a5c63582/src/lib/codemirror.component.ts#L98

You might make sure you've included your plugin correctly from the first step of use https://github.com/TypeCtrl/ngx-codemirror#use or make sure the codemirror instance has been created when your code runs

scttcper avatar Nov 05 '18 21:11 scttcper

capture d ecran 2018-11-06 a 18 05 53

console.log(this.codeMirror.codeMirror), instance is ok. Viewer is ok, but when addons are loaded, no functions are inherited

capture d ecran 2018-11-06 a 18 05 32

Same problem with the meta mod: capture d ecran 2018-11-06 a 18 16 10

Just for information, I use angular 6

johaven avatar Nov 06 '18 17:11 johaven

This seems to be the case for all the addons

johaven avatar Nov 11 '18 08:11 johaven

After a lot of debugging, it seems require creates a scoped instance of CodeMirror. Addons have to find a global instance of CodeMirror in plain mode to correctly work. fromTextArea doesn't return all CodeMirror object (this is why i did not find the addons functions).

My final solution is to mix global instance and this component. For this i need to declare in angular.json all the scripts to import:

"scripts": [
              "node_modules/codemirror/lib/codemirror.js",
              "node_modules/codemirror/mode/meta.js",
              "node_modules/codemirror/addon/mode/loadmode.js"
            ]

If we use fromTextArea from scoped instance, the setOption function will not work. In ngx-codemirror component replace:

declare var require: any; // remove this
const { fromTextArea } = require('codemirror');
this.codeMirror = fromTextArea(this.ref.nativeElement, this._options);

by:

declare var CodeMirror: any; // replaced by "var require..."
this.codeMirror = CodeMirror.fromTextArea(this.ref.nativeElement, this._options);

In my internal component i use declare var CodeMirror: any to access all functions (ref is the childView of ngx-codemirror component)

ngAfterViewInit() {
        CodeMirror.modeURL = this.modeUrl
        const mode = CodeMirror.findModeByFileName(this.file.name).mode // this function is loaded by meta addon
        CodeMirror.autoLoadMode(this.ref.codeMirror, mode) // this function is loaded by loadmode addon
        this.ref.codeMirror.setOption('mode', mode)
    }

johaven avatar Nov 11 '18 13:11 johaven

Importing addons like modes as described in the README works perfectly. Here's my main.ts file:

import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

import { AppModule } from './app/app.module';
import { environment } from './environments/environment';

// Import your required language in main.ts or at the root of your application
// see https://codemirror.net/mode/index.html
import 'codemirror/mode/javascript/javascript';
import 'codemirror/mode/css/css';
import 'codemirror/mode/htmlmixed/htmlmixed';

import 'codemirror/addon/edit/matchbrackets';
import 'codemirror/addon/edit/closetag';
import 'codemirror/addon/selection/active-line';


if (environment.production) {
  enableProdMode();
}

platformBrowserDynamic().bootstrapModule(AppModule)
  .catch(err => console.error(err));

and here's my options object to enable the above addons:

editorOptions = {
  lineNumbers: true,
  autoCloseTags: true,
  styleActiveLine: true,
  theme: 'monokai',
  mode: 'text/html',
  // define Emmet output profile
  profile: 'xhtml'
};

Btw, thank you a lot @scttcper for this awesome ngx wrapper for codemirror! 🙌 Cheers!

maximelafarie avatar Nov 21 '18 13:11 maximelafarie

This only works for addons that affect the editor (CodeMirror.fromTextArea)

johaven avatar Nov 21 '18 13:11 johaven

@johaven I got it! Good to know. 👍

maximelafarie avatar Nov 21 '18 13:11 maximelafarie

@johaven i'm not sure how to fix it for your case. You might try copying out the component and instantiating codemirror as you require, unless there's a way to make it work for everyone's general use.

scttcper avatar Nov 21 '18 17:11 scttcper

@scttcper This is what i do for the time being. But if you replace the 2 lines of code, it should work for everyone:

declare var CodeMirror: any; // replaced by "var require..."
this.codeMirror = CodeMirror.fromTextArea(this.ref.nativeElement, this._options);

If people want to use other addons like me, they will only have to load the libraries via the Angular scripts tag above, for others it will work the same way.

johaven avatar Nov 21 '18 18:11 johaven

I am attempting to do something similar except in my case is to dynamically define the mode and load it on the fly based on a matrix of user provided data... @johaven could you provide a StackBlitz showing what you did to get loadmode functional? because I am hitting a brick wall trying to deduce this thread.

james-powis avatar Feb 06 '19 23:02 james-powis

I do not have much time but I will give you my configuration.

angular.json

"scripts": [
              "node_modules/codemirror/lib/codemirror.js",
              "node_modules/codemirror/mode/meta.js",
              "node_modules/codemirror/addon/mode/loadmode.js",
              "node_modules/codemirror/addon/mode/overlay.js"
            ]

codemirror.component.ts diff I declare this modified component in my module. capture d ecran 2019-02-09 a 09 59 25

Usage example

import {AfterViewInit, Component, Input, OnInit, ViewChild, ViewEncapsulation} from '@angular/core'
import {HttpClient} from '@angular/common/http'

declare var CodeMirror: any

@Component({
    selector: 'app-files-viewer-text',
    encapsulation: ViewEncapsulation.None,
    styles: [`
      .CodeMirror {
        height: 100%
      }
    `],
    template: `
      <div [style.height.px]="currentHeight">
        <ngx-codemirror #CodeMirror [(ngModel)]="content" [options]=options></ngx-codemirror>
      </div>`
})
export class FilesViewerTextComponent implements OnInit, AfterViewInit {
    @ViewChild('CodeMirror') ref: any
    @Input() currentHeight: number
    @Input() file: any
    @Input() fileUrl: string
    private readonly modeUrl = '/static/assets/codemirror/mode/%N/%N.js'
    public content: string
    public options: any = {lineNumbers: true, readOnly: true, theme: 'material', mode: 'null'}
    private mode: string = null

    constructor(private http: HttpClient) {
        CodeMirror.modeURL = this.modeUrl
    }

    ngOnInit() {
        const detectedMode = CodeMirror.findModeByFileName(this.file.name)
        if (detectedMode) {
            this.mode = detectedMode.mode
            this.http.get(this.fileUrl, {responseType: 'text'}).subscribe((data: string) => this.content = data)
        } else {
            this.content = 'This file contains binary data that can not be read'
        }
    }

    ngAfterViewInit() {
        if (this.mode) {
            CodeMirror.autoLoadMode(this.ref.codeMirror, this.mode)
            this.ref.codeMirror.setOption('mode', this.mode)
        }
    }
}

johaven avatar Feb 09 '19 09:02 johaven

Hello, i also had problems to use the mergeView addon, but finally made it, so here is my sample codemirror-merge-view

Fillonj avatar Feb 02 '22 15:02 Fillonj

@scttcper this can be closed

sp90 avatar Feb 01 '23 08:02 sp90