diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1b66f1e --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.mint +dist \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..cc2ceea --- /dev/null +++ b/README.md @@ -0,0 +1,43 @@ +# mint-codemirror +This package provides a component for the [CodeMirror](https://codemirror.net) code editor to be used in Mint projects. + +## Installation +To use the component just add this to the `dependencies` field of the projects +`mint.json` file. + +``` +"mint-codemirror": { + "repository": "https://github.com/mint-lang/mint-codemirror", + "constraint": "1.0.0 <= v < 2.0.0" +} +``` + +## Usage +To get the basic component without any modes and the default theme, just add the component to your render function: + +``` +component Main { + fun onChange (value : String) : Void { + do { + Debug.log(value) + } + } + + fun render : Html { + + } +} +``` + +## Properties +The following properties are available: +* `javaScripts : Array(String)` - URLs for the JavaScript files that are needed for CodeMirror to work. This can come from a CDN or from local files. This should contian the base JavaScript and any other addons or modes that is required. +* `styles : Array(String)` - URLs for the CSS files that are that are needed for CodeMirror to work. This can come from a CDN or from local files. This should contain the base CSS and any other files for themes. +* `loadingContent : Html` - This is shown until the all the files have loaded and editor is ready. +* `onChange : Function(String, Void)` - This is called when the content changes. +* `value : String` - When provided this value will be in the editor. + +## Advanced Usage +This is an example for using the all of the properties: +``` +``` diff --git a/mint.json b/mint.json new file mode 100644 index 0000000..43ed139 --- /dev/null +++ b/mint.json @@ -0,0 +1,12 @@ +{ + "name": "mint-codemirror", + "source-directories": [ + "source" + ], + "dependencies": { + "mint-core": { + "repository": "https://github.com/mint-lang/mint-core", + "constraint": "0.0.0 <= v < 1.0.0" + } + } +} diff --git a/source/AssetLoader.mint b/source/AssetLoader.mint new file mode 100644 index 0000000..e1dc6b2 --- /dev/null +++ b/source/AssetLoader.mint @@ -0,0 +1,35 @@ +/* Functions for loading assets (JavaScripts, CSS) asyncronously. */ +module AssetLoader { + /* Loads the stylesheet from the given URL. */ + fun loadStyle (url : String) : Promise(Never, Void) { + ` + new Promise((resolve, reject) => { + let link = document.createElement('link') + link.rel = "stylesheet" + document.head.appendChild(link) + link.onload = resolve + link.href = url + }) + ` + } + + /* Loads the script from the given URL. */ + fun loadScript (url : String) : Promise(Never, Void) { + ` + new Promise((resolve, reject) => { + let script = document.createElement('script') + document.body.appendChild(script) + script.onload = () => { + document.body.removeChild(script) + resolve() + } + script.src = url + }) + ` + } + + /* Waits for all promises to load. */ + fun loadAll (promises : Array(Promise(a, b))) : Promise(a, b) { + `Promise.all(promises)` + } +} diff --git a/source/CodeMirror.mint b/source/CodeMirror.mint new file mode 100644 index 0000000..3493fd7 --- /dev/null +++ b/source/CodeMirror.mint @@ -0,0 +1,136 @@ +/* A component that integrates the CodeMirror editor. */ +component CodeMirror { + /* The JavaScript files of Codemirror to load, either locally or from a CDN. */ + property javaScripts : Array(String) = [ + "https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.39.0" \ + "/codemirror.min.js" + ] + + /* The CSS files of Codemirror to load, either locally or from a CDN. */ + property styles : Array(String) = [ + "https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.39.0" \ + "/codemirror.min.css" + ] + + /* The content to display until the editor is loaded. */ + property loadingContent : Html = Html.empty() + + /* Handler for the change event. */ + property onChange : Function(String, Void) = (\value : String => void) + + /* The value of the editor. */ + property value : String = "" + + /* The theme of the editor. */ + property theme : String = "" + + /* The mode of the editor. */ + property mode : String = "" + + /* Whether or not show line numbers. */ + property lineNumbers : Bool = true + + /* Initializes the editor for the given dom element. */ + fun initializeEditor (element : Dom.Element) : Void { + do { + javaScripts + |> Array.map(AssetLoader.loadScript) + |> AssetLoader.loadAll() + + styles + |> Array.map(AssetLoader.loadStyle) + |> AssetLoader.loadAll() + + ` + (() => { + if (this.editor) { return } + + this.editor = CodeMirror.fromTextArea(element, { + lineNumbers: this.lineNumbers, + theme: this.theme, + mode: this.mode, + }) + + this.editor.on('change', (value) => { + this.onChange(this.editor.getValue()) + }) + + this.forceUpdate() + })() + ` + } + } + + /* After an update, update the underlying editor instance. */ + fun componentDidUpdate : Void { + ` + (() => { + if (this.editor) { + if (this.props.value != null) { + if (this.editor.getValue() !== this.props.value) { + this.editor.setValue(this.props.value); + } + } + } + })() + ` + } + + style editor { + display: {display}; + } + + /* + Returns the content for the `display` property, + to hide the textarea until it's ready. + */ + get display : String { + if (`this.editor`) { + "" + } else { + "none" + } + } + + /* Renders the component. */ + fun render : Array(Html) { + [ + , + if (`this.editor`) { + Html.empty() + } else { + loadingContent + } + ] + } +} + +record Main.State { + value : String +} +component Main { + state : Main.State { + value = "function(){ \n
{ this.text }
\n}" + } + + fun onChange (value : String) : Void { + next { state | value = value } + } + + fun render : Html { + + } +}