diff --git a/README.md b/README.md index 0363943..7375308 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ SilverStripe CodeEditorField =================================== -This module adds a CodeEditorField that uses Ace Editor (http://ace.c9.io/) to let you edit code (mostly HTML or JavaScript). I comes in two forms: +This module adds a CodeEditorField that uses Ace Editor (http://ace.c9.io/) to let you edit code (mostly HTML or JavaScript). [Try it out here](https://ace.c9.io/build/kitchen-sink.html). It comes in two forms: * A regular CodeEditorField that can be used on an HTMLText field similar to HTMLEditorField * An optional replacement code view for TinyMCE that uses a CodeEditorField @@ -22,49 +22,68 @@ Documentation Installation Instructions ------------------------- -1. Place the files in a directory called codeeditorfield in the root of your SilverStripe installation -2. Visit yoursite.com/dev/build to rebuild the database +1. Place the files in a directory called codeeditorfield in the root of your SilverStripe installation. You can most easily do this with `composer require nathancox/codeeditorfield` +2. Configure your site's `config.yml` to define your defaults (optional). +3. Visit yoursite.com/dev/build to rebuild the database Usage Overview -------------- +Configuration via `config.yml`: + +```yaml + +--- +Name: codeeditorfield +--- + +CodeEditorField: + # These are the pre-defined defaults for dark/light themes + default_dark_theme: 'monokai' + default_light_theme: 'github' + + # This will overwrite the above settings + default_theme: 'tomorrow' + +``` + Using CodeEditorField in getCMSFields: ```php - $fields->addFieldToTab('Root.Content', $codeField = new CodeEditorField('ExtraTags', 'Extra tags')); - $codeField->addExtraClass('stacked'); - - // set the height of the field (defaults to 8) - $codeField->setRows(15); - - // set the syntax mode to javascript (defaults to html) - $codeField->setMode('javascript'); +$fields->addFieldToTab('Root.Content', $codeField = new CodeEditorField('Configuration', 'Configuration')); +// set the field to use the full width of the CMS (optional, not included in screenshot) +$codeField->addExtraClass('stacked'); - // optional - set theme (see codeeditorfield/thirdparty/ace/src-noconflict/theme-xxx.js files for available themes) - $codeField->setTheme('twilight'); +// set the height of the field (defaults to 8) +$codeField->setRows(30); -``` +// set the syntax mode to yaml (defaults to html) +$codeField->setMode('yaml'); -produces +// optional - set theme (see codeeditorfield/thirdparty/ace/src-noconflict/theme-xxx.js files for available themes) +$codeField->setTheme('twilight'); -![example codeeditorfield](http://static.flyingmonkey.co.nz/github/silverstripe-codeeditorfield/codeeditorfield-1.png) +``` +produces the following: +![example codeeditorfield](./screenshot.png) +__Note: If you opt not to set `default_theme` and don't set the theme specifically on the field, you will have the option to toggle between Dark and Light.__ -Replace the code editor in TinyMCE: -```php +To replace the code editor in TinyMCE: -// copy this into your project's getCMSFields (you can find this code in codeeditorfield/_config.php) +```php - HtmlEditorConfig::get('cms')->enablePlugins(array( - 'aceeditor' => sprintf('../../../codeeditorfield/javascript/tinymce/editor_plugin_src.js') - )); - HtmlEditorConfig::get('cms')->insertButtonsBefore('fullscreen', 'aceeditor'); - HtmlEditorConfig::get('cms')->removeButtons('code'); +// copy this into your project's getCMSFields +HtmlEditorConfig::get('cms')->enablePlugins(array( + 'aceeditor' => sprintf('../../../codeeditorfield/javascript/tinymce/editor_plugin_src.js') +)); +HtmlEditorConfig::get('cms')->insertButtonsBefore('fullscreen', 'aceeditor'); +HtmlEditorConfig::get('cms')->removeButtons('code'); ``` diff --git a/code/CodeEditorField.php b/code/CodeEditorField.php index b3ea8c2..1ba9d23 100644 --- a/code/CodeEditorField.php +++ b/code/CodeEditorField.php @@ -14,13 +14,33 @@ class CodeEditorField extends TextareaField { /** * @var string default_theme */ - private static $default_theme; + private static $default_theme = null; + + /** + * @var string default_dark_theme + */ + private static $default_dark_theme = 'monokai'; + + /** + * @var string default_light_theme + */ + private static $default_light_theme = 'github'; /** * @var string mode */ protected $mode; + /** + * @var string dark_theme + */ + protected $dark_theme; + + /** + * @var string light_theme + */ + protected $light_theme; + /** * @var string theme */ @@ -37,7 +57,9 @@ public function getAttributes() { array( 'data-mode' => $this->getMode(), 'data-ace-path' => $this->getAcePath(), - 'data-theme' => $this->getTheme() + 'data-theme' => $this->getTheme(), + 'data-dark' => $this->getDarkTheme(), + 'data-light' => $this->getLightTheme() ) ); } @@ -48,8 +70,6 @@ function Field($properties = array()) { Requirements::javascript($acePath . "ace.js"); Requirements::javascript($acePath . "mode-" . $this->getMode() . ".js"); - // Requirements::javascript($acePath . "worker-" . $this->getMode() . ".js"); - Requirements::javascript("codeeditorfield/javascript/CodeEditorField.js"); Requirements::css("codeeditorfield/css/CodeEditorField.css"); @@ -71,7 +91,23 @@ function setTheme($theme) { } function getTheme() { - return $this->theme ? $this->theme : $this->config()->get('default_theme'); + if ($this->getDefaultTheme()){ + return $this->theme ? $this->theme : $this->config()->get('default_theme'); + } else { + return $this->theme ? $this->theme : $this->config()->get('default_dark_theme'); + } + } + + function getDefaultTheme() { + return $this->config()->get('default_theme'); + } + + function getDarkTheme() { + return $this->dark_theme ? $this->dark_theme : $this->config()->get('default_dark_theme'); + } + + function getLightTheme() { + return $this->light_theme ? $this->light_theme : $this->config()->get('default_light_theme'); } function getAcePath() { diff --git a/css/CodeEditorField.css b/css/CodeEditorField.css index e94ad8c..c05dfc7 100644 --- a/css/CodeEditorField.css +++ b/css/CodeEditorField.css @@ -1,64 +1,64 @@ .codeeditor .ace_editor { - width:100%; - max-width:512px; - min-height:150px; - - border: 1px solid #B3B3B3; - border-radius: 4px 4px 4px 4px; - + width: 100%; + max-width: 512px; + min-height: 150px; + border: 1px solid #B3B3B3; + border-radius: 4px 4px 4px 4px; } - - .stacked.codeeditor .ace_editor { max-width:none; } .codeeditor-button-bar { padding-top:5px; + width: 512px; } .codeeditor .ss-ui-button { margin-right:5px; } + +#mode-label { + display: inline-block; + position: relative; + float: right; + text-transform: uppercase; + color: #777777; + font-style: italic; +} .codeeditor .ss-ui-button.active { - background: linear-gradient(#FFFFFF, #E6E6E6) repeat scroll 0 0 transparent; + background: linear-gradient(#338dc1, #287099);) repeat scroll 0 0 transparent; + color: white; + text-shadow: none; border: 1px solid #B3B3B3; box-shadow: 0 0 5px #B3B3B3 inset; } - - .field.checkbox.stacked { padding-left:0; } - - - .ui-dialog .htmleditorfield-codeform { position:absolute; - top:0; - bottom:0; - left:0; - right:0; + top:0; + bottom:0; + left:0; + right:0; margin:0; padding-bottom:62px; } - .htmleditorfield-codeform .Actions { - position:absolute; - bottom:0; - - right:0; - margin:0; - text-align:right; - } - - .htmleditorfield-codeform fieldset .Actions { - bottom:-62px; - } - +.htmleditorfield-codeform .Actions { + position:absolute; + bottom:0; + right:0; + margin:0; + text-align:right; +} +.htmleditorfield-codeform fieldset .Actions { + bottom:-62px; +} .htmleditorfield-codeform fieldset { height:100%; @@ -69,44 +69,36 @@ .htmleditorfield-codeform fieldset .CompositeField { } - .htmleditorfield-codeform fieldset .CompositeField .Field { - } +.htmleditorfield-codeform fieldset .CompositeField .Field { +} .htmleditorfield-codeform .field.codeeditor { margin:0; padding:0; - position:absolute; - top:40px; - bottom:0px; - left:0; - right:0; + top:40px; + bottom:0px; + left:0; + right:0; } .htmleditorfield-codeform .ace_editor { border:0; border-radius:0; - - position:absolute; - top:0px; - bottom:0px; - left:0; - right:0; - - + top:0px; + bottom:0px; + left:0; + right:0; } - .htmleditorfield-codeform .codeeditor-button-bar { - position:absolute; - bottom:-62px; - left:0; - - margin:0; - min-height:24px; - - padding: 18px 16px; - min-height:30px; - } +.htmleditorfield-codeform .codeeditor-button-bar { + position:absolute; + bottom:-62px; + left:0; + margin:0; + padding: 18px 16px; + min-height:30px; +} diff --git a/javascript/CodeEditorField.js b/javascript/CodeEditorField.js index 5fd35d4..8479051 100644 --- a/javascript/CodeEditorField.js +++ b/javascript/CodeEditorField.js @@ -1,96 +1,107 @@ (function($) { -$.entwine('ss', function($) { -$('textarea.codeeditor').entwine({ - Editor: false, + $.entwine('ss', function($) { + $('textarea.codeeditor').entwine({ + Editor: false, - onmatch: function() { - var textarea = this; + onmatch: function() { + var textarea = this; - // hide the textarea - this.hide(); + // hide the textarea + this.hide(); - // create the editor div - var divID = this.attr('id') + '_Ace'; - var $div = this.getEditorEl(); - - $div.insertAfter(this); + // create the editor div + var divID = this.attr('id') + '_Ace'; + var $div = this.getEditorEl(); + + $div.insertAfter(this); - ace.config.set('modePath', this.data('ace-path')); - ace.config.set('workerPath', this.data('ace-path')); - ace.config.set('themePath', this.data('ace-path')); + ace.config.set('modePath', this.data('ace-path')); + ace.config.set('workerPath', this.data('ace-path')); + ace.config.set('themePath', this.data('ace-path')); - // apply the editor to the div - var editor = ace.edit(divID); + // apply the editor to the div + var editor = ace.edit(divID); - // make the editor update the textarea content - editor.getSession().setValue(textarea.val()); - editor.getSession().on('change', function(){ - textarea.val(editor.getSession().getValue()); + // make the editor update the textarea content + editor.getSession().setValue(textarea.val()); + editor.getSession().on('change', function(){ + textarea.val(editor.getSession().getValue()); + }); + + editor.setAutoScrollEditorIntoView(false); + editor.getSession().setTabSize(2); + editor.setShowPrintMargin(false); + editor.session.setWrapLimitRange(null, null); + + // set the mode (ie syntax highlighting) + editor.getSession().setMode('ace/mode/' + this.data('mode')); + + // load a theme if one is set + if (this.data('theme')) { + editor.setTheme('ace/theme/' + this.data('theme')); + } + + var lineHeight = (editor.renderer.lineHeight > 1 ? editor.renderer.lineHeight : 16) + + $div.css('min-height', lineHeight * textarea.attr('rows') + 35 + 'px'); + + editor.resize(true); + this.setEditor(editor); + this.addClass('done'); + }, + + getEditorEl: function() { + return $('#' + this.attr('id') + '_Ace'); + }, + + getWordWrapEl: function() { + return $('#' + this.attr('id') + '_Ace_word_wrap'); + } + + }); + + $('.codeeditor .ss-ui-button').entwine({ + onmatch: function() { + }, + + onmouseup: function() { + this.blur(); + }, + + getEditor: function() { + return $(this.closest('.middleColumn').find('textarea').first()).getEditor(); + } + }); + + // Word wrap toggle + $('.codeeditor .ace-word-wrap-button').entwine({ + onclick: function() { + var editor = this.getEditor(); + if (editor.session.getUseWrapMode()) { + editor.session.setUseWrapMode(false); + this.removeClass('active'); + } else { + editor.session.setUseWrapMode(true); + this.addClass('active'); + } + return false; + } }); - editor.setAutoScrollEditorIntoView(false); - editor.getSession().setTabSize(2); - editor.setShowPrintMargin(false); - editor.session.setWrapLimitRange(null, null); - - // set the mode (ie syntax highlighting) - editor.getSession().setMode("ace/mode/"+this.data('mode')); - - // load a theme if one is set - if (this.data('theme')) { - editor.setTheme('ace/theme/' + this.data('theme')); - } - - var lineHeight = (editor.renderer.lineHeight > 1 ? editor.renderer.lineHeight : 16) - - $div.css('min-height', lineHeight * textarea.attr('rows') + 35 + 'px'); - - editor.resize(true); - this.setEditor(editor); - this.addClass('done'); - }, - - getEditorEl: function() { - return $('#' + this.attr('id') + '_Ace'); - }, - - - getWordWrapEl: function() { - return $('#' + this.attr('id') + '_Ace_word_wrap'); - } - -}); - -$('.codeeditor .ss-ui-button').entwine({ - onmatch: function() { - // this.button(); - }, - onmouseup: function() { - this.blur(); - }, - getEditor: function() { - return $(this.closest('.middleColumn').find('textarea').first()).getEditor(); - } -}); - - -$('.codeeditor .ace-word-wrap-button').entwine({ - onclick: function() { - var editor = this.getEditor(); - if (editor.session.getUseWrapMode()) { - editor.session.setUseWrapMode(false); - this.removeClass('active'); - } else { - editor.session.setUseWrapMode(true); - this.addClass('active'); - } - return false; - } -}); -}); - - - + // Light/Dark toggle (only included if possible) + $('.codeeditor .ace-theme-button').entwine({ + onclick: function() { + var editor = this.getEditor(); + var data = this.closest('.middleColumn').find('textarea').first(); + if (editor.getTheme() === ('ace/theme/' + data.data('dark'))) { + editor.setTheme('ace/theme/' + data.data('light')); + } else { + editor.setTheme('ace/theme/' + data.data('dark')); + } + return false; + } + }); + }); })(jQuery); diff --git a/screenshot.png b/screenshot.png new file mode 100644 index 0000000..714ae09 Binary files /dev/null and b/screenshot.png differ diff --git a/templates/CodeEditorField.ss b/templates/CodeEditorField.ss index d0117c7..ffed50c 100644 --- a/templates/CodeEditorField.ss +++ b/templates/CodeEditorField.ss @@ -1,7 +1,16 @@
\ No newline at end of file