2017-08-22 10:53:26 +03:00
import Component from '@ember/component' ;
2017-06-22 15:02:38 +03:00
import ShortcutsMixin from 'ghost-admin/mixins/shortcuts' ;
import ctrlOrCmd from 'ghost-admin/utils/ctrl-or-cmd' ;
2017-05-29 21:50:03 +03:00
import formatMarkdown from 'ghost-admin/utils/format-markdown' ;
2017-08-22 10:53:26 +03:00
import { assign } from '@ember/polyfills' ;
import { computed } from '@ember/object' ;
import { htmlSafe } from '@ember/string' ;
import { isEmpty , typeOf } from '@ember/utils' ;
import { run } from '@ember/runloop' ;
2017-10-30 12:38:01 +03:00
import { inject as service } from '@ember/service' ;
2017-05-08 13:35:42 +03:00
2017-06-22 15:02:38 +03:00
export default Component . extend ( ShortcutsMixin , {
2017-05-08 13:35:42 +03:00
2017-10-30 12:38:01 +03:00
config : service ( ) ,
notifications : service ( ) ,
settings : service ( ) ,
2017-07-28 16:21:09 +03:00
2017-05-08 21:15:56 +03:00
classNames : [ 'gh-markdown-editor' ] ,
classNameBindings : [
'_isFullScreen:gh-markdown-editor-full-screen' ,
'_isSplitScreen:gh-markdown-editor-side-by-side'
] ,
2017-05-08 13:35:42 +03:00
// Public attributes
autofocus : false ,
2017-05-10 18:16:36 +03:00
imageMimeTypes : null ,
2017-05-08 21:15:56 +03:00
isFullScreen : false ,
2018-04-18 17:52:45 +03:00
markdown : null ,
2017-05-08 13:35:42 +03:00
options : null ,
placeholder : '' ,
2017-11-24 13:41:09 +03:00
showMarkdownHelp : false ,
2017-05-08 13:35:42 +03:00
uploadedImageUrls : null ,
2017-11-24 21:53:19 +03:00
shortcuts : null ,
2017-05-08 13:35:42 +03:00
// Private
_editor : null ,
2017-08-02 10:05:59 +03:00
_editorFocused : false ,
2017-05-08 21:15:56 +03:00
_isFullScreen : false ,
_isSplitScreen : false ,
2017-10-03 08:31:10 +03:00
_isHemingwayMode : false ,
2017-05-08 13:35:42 +03:00
_isUploading : false ,
2017-08-02 10:05:59 +03:00
_showUnsplash : false ,
2017-05-08 13:35:42 +03:00
_statusbar : null ,
_toolbar : null ,
2017-05-08 21:15:56 +03:00
_uploadedImageUrls : null ,
2017-05-08 13:35:42 +03:00
2018-01-11 20:43:23 +03:00
// Closure actions
onChange ( ) { } ,
onFullScreenToggle ( ) { } ,
onImageFilesSelected ( ) { } ,
onPreviewToggle ( ) { } ,
onSplitScreenToggle ( ) { } ,
2017-05-08 13:35:42 +03:00
simpleMDEOptions : computed ( 'options' , function ( ) {
let options = this . get ( 'options' ) || { } ;
let defaultOptions = {
2017-05-15 19:51:19 +03:00
// use our Showdown config with sanitization for previews
previewRender ( markdown ) {
return formatMarkdown ( markdown ) ;
} ,
// Ghost-specific SimpleMDE toolbar config - allows us to create a
// bridge between SimpleMDE buttons and Ember actions
2017-05-08 13:35:42 +03:00
toolbar : [
'bold' , 'italic' , 'heading' , '|' ,
'quote' , 'unordered-list' , 'ordered-list' , '|' ,
2017-05-10 18:16:36 +03:00
'link' ,
{
name : 'image' ,
action : ( ) => {
this . _openImageFileDialog ( ) ;
} ,
className : 'fa fa-picture-o' ,
title : 'Upload Image(s)'
} ,
'|' ,
2017-05-12 11:06:56 +03:00
{
name : 'preview' ,
action : ( ) => {
this . _togglePreview ( ) ;
} ,
className : 'fa fa-eye no-disable' ,
2017-10-03 08:31:10 +03:00
title : 'Render Preview (Ctrl-Alt-R)' ,
useCtrlOnMac : true
2017-05-12 11:06:56 +03:00
} ,
2017-05-08 21:15:56 +03:00
{
name : 'side-by-side' ,
action : ( ) => {
this . send ( 'toggleSplitScreen' ) ;
} ,
className : 'fa fa-columns no-disable no-mobile' ,
2017-10-03 08:31:10 +03:00
title : 'Side-by-side Preview (Ctrl-Alt-P)' ,
useCtrlOnMac : true
2017-05-08 13:35:42 +03:00
} ,
'|' ,
2017-05-18 13:08:50 +03:00
{
name : 'spellcheck' ,
action : ( ) => {
this . _toggleSpellcheck ( ) ;
} ,
className : 'fa fa-check' ,
2017-10-03 08:31:10 +03:00
title : 'Spellcheck (Ctrl-Alt-S)' ,
useCtrlOnMac : true
2017-05-18 13:08:50 +03:00
} ,
2017-07-28 16:21:09 +03:00
{
2017-10-03 08:31:10 +03:00
name : 'hemingway' ,
2017-07-28 16:21:09 +03:00
action : ( ) => {
2017-10-03 08:31:10 +03:00
this . _toggleHemingway ( ) ;
2017-07-28 16:21:09 +03:00
} ,
className : 'fa fa-h-square' ,
2017-10-03 08:31:10 +03:00
title : 'Hemingway Mode (Ctrl-Alt-H)' ,
useCtrlOnMac : true
2017-07-28 16:21:09 +03:00
} ,
2017-05-08 13:35:42 +03:00
{
name : 'guide' ,
action : ( ) => {
2017-11-24 13:41:09 +03:00
this . send ( 'toggleMarkdownHelp' ) ;
2017-05-08 13:35:42 +03:00
} ,
className : 'fa fa-question-circle' ,
title : 'Markdown Guide'
}
] ,
2017-05-15 19:51:19 +03:00
// disable shortcuts for side-by-side and fullscreen because they
// trigger interal SimpleMDE methods that will result in broken
// layouts
2017-05-10 18:16:36 +03:00
shortcuts : {
2017-05-12 11:06:56 +03:00
toggleFullScreen : null ,
togglePreview : null ,
2017-06-22 18:36:40 +03:00
toggleSideBySide : null ,
drawImage : null
2017-05-10 18:16:36 +03:00
} ,
2017-05-15 19:51:19 +03:00
// only include the number of words in the status bar
2017-05-08 13:35:42 +03:00
status : [ 'words' ]
} ;
2017-09-20 13:19:48 +03:00
if ( this . get ( 'settings.unsplash.isActive' ) ) {
2017-08-02 10:05:59 +03:00
let image = defaultOptions . toolbar . findBy ( 'name' , 'image' ) ;
let index = defaultOptions . toolbar . indexOf ( image ) + 1 ;
defaultOptions . toolbar . splice ( index , 0 , {
name : 'unsplash' ,
action : ( ) => {
this . send ( 'toggleUnsplash' ) ;
} ,
className : 'fa fa-camera' ,
title : 'Add Image from Unsplash'
} ) ;
}
2017-05-08 13:35:42 +03:00
return assign ( defaultOptions , options ) ;
} ) ,
2017-06-22 15:02:38 +03:00
init ( ) {
this . _super ( ... arguments ) ;
2017-11-24 21:53:19 +03:00
let shortcuts = { } ;
2017-08-02 10:05:59 +03:00
shortcuts [ ` ${ ctrlOrCmd } +shift+i ` ] = { action : 'openImageFileDialog' } ;
2017-10-03 08:31:10 +03:00
shortcuts [ 'ctrl+alt+r' ] = { action : 'togglePreview' } ;
shortcuts [ 'ctrl+alt+p' ] = { action : 'toggleSplitScreen' } ;
shortcuts [ 'ctrl+alt+s' ] = { action : 'toggleSpellcheck' } ;
shortcuts [ 'ctrl+alt+h' ] = { action : 'toggleHemingway' } ;
2017-11-24 21:53:19 +03:00
this . shortcuts = shortcuts ;
2017-06-22 15:02:38 +03:00
} ,
2017-05-08 13:35:42 +03:00
// extract markdown content from single markdown card
didReceiveAttrs ( ) {
this . _super ( ... arguments ) ;
let uploadedImageUrls = this . get ( 'uploadedImageUrls' ) ;
if ( ! isEmpty ( uploadedImageUrls ) && uploadedImageUrls !== this . _uploadedImageUrls ) {
this . _uploadedImageUrls = uploadedImageUrls ;
// must be done afterRender to avoid double modify of mobiledoc in
// a single render
run . scheduleOnce ( 'afterRender' , this , ( ) => {
this . _insertImages ( uploadedImageUrls ) ;
} ) ;
}
2018-01-17 16:27:37 +03:00
// focus the editor when the markdown value changes, this is necessary
// because both the autofocus and markdown values can change without a
// re-render, eg. navigating from edit->new
2018-04-18 17:52:45 +03:00
if ( this . get ( 'autofocus' ) && this . _editor && this . get ( 'markdown' ) !== this . _editor . value ( ) ) {
2018-01-17 16:27:37 +03:00
this . send ( 'focusEditor' ) ;
}
2017-05-08 21:15:56 +03:00
// use internal values to avoid updating bound values
if ( ! isEmpty ( this . get ( 'isFullScreen' ) ) ) {
this . set ( '_isFullScreen' , this . get ( 'isFullScreen' ) ) ;
}
if ( ! isEmpty ( this . get ( 'isSplitScreen' ) ) ) {
this . set ( '_isSplitScreen' , this . get ( 'isSplitScreen' ) ) ;
}
this . _updateButtonState ( ) ;
2017-05-08 13:35:42 +03:00
} ,
2017-06-22 15:02:38 +03:00
didInsertElement ( ) {
this . _super ( ... arguments ) ;
this . registerShortcuts ( ) ;
2018-01-08 22:10:29 +03:00
// HACK: iOS will scroll the body up when activating the keyboard, this
// causes problems in the CodeMirror based editor because iOS doesn't
// scroll the cursor and other measurement elements which results in
// rather unfriendly behaviour with text appearing in seemingly random
// places and an inability to select things properly
//
// To get around this we use a raf loop that constantly makes sure the
// body scrollTop is 0 when the editor is on screen
let iOS = ! ! navigator . platform && /iPad|iPhone|iPod/ . test ( navigator . platform ) ;
if ( iOS ) {
this . _preventBodyScroll ( ) ;
}
} ,
willDestroyElement ( ) {
2018-01-08 22:30:16 +03:00
if ( this . get ( '_isSplitScreen' ) ) {
this . _disconnectSplitPreview ( ) ;
}
this . removeShortcuts ( ) ;
2018-01-08 22:10:29 +03:00
this . _super ( ... arguments ) ;
2018-01-08 22:30:16 +03:00
2018-01-08 22:10:29 +03:00
if ( this . _preventBodyScrollId ) {
window . cancelAnimationFrame ( this . _preventBodyScrollId ) ;
}
} ,
2018-01-11 20:43:23 +03:00
actions : {
2018-04-18 17:52:45 +03:00
// trigger external update, any mobiledoc updates are handled there
2018-01-11 20:43:23 +03:00
updateMarkdown ( markdown ) {
2018-04-18 17:52:45 +03:00
this . onChange ( markdown ) ;
2018-01-11 20:43:23 +03:00
} ,
// store a reference to the simplemde editor so that we can handle
// focusing and image uploads
setEditor ( editor ) {
this . _editor = editor ;
// disable CodeMirror's drag/drop handling as we want to handle that
// in the parent gh-editor component
this . _editor . codemirror . setOption ( 'dragDrop' , false ) ;
// default to spellchecker being off
this . _editor . codemirror . setOption ( 'mode' , 'gfm' ) ;
// add non-breaking space as a special char
2018-03-19 14:54:54 +03:00
// eslint-disable-next-line no-control-regex
2018-01-11 20:43:23 +03:00
this . _editor . codemirror . setOption ( 'specialChars' , /[\u0000-\u001f\u007f-\u009f\u00ad\u061c\u200b-\u200f\u2028\u2029\ufeff\xa0]/g ) ;
// HACK: move the toolbar & status bar elements outside of the
// editor container so that they can be aligned in fixed positions
let container = this . $ ( ) . closest ( '.gh-editor' ) . find ( '.gh-editor-footer' ) ;
this . _toolbar = this . $ ( '.editor-toolbar' ) ;
this . _statusbar = this . $ ( '.editor-statusbar' ) ;
this . _toolbar . appendTo ( container ) ;
this . _statusbar . appendTo ( container ) ;
this . _updateButtonState ( ) ;
} ,
// used by the title input when the TAB or ENTER keys are pressed
focusEditor ( position = 'bottom' ) {
this . _editor . codemirror . focus ( ) ;
if ( position === 'bottom' ) {
this . _editor . codemirror . execCommand ( 'goDocEnd' ) ;
} else if ( position === 'top' ) {
this . _editor . codemirror . execCommand ( 'goDocStart' ) ;
}
return false ;
} ,
// HACK FIXME (PLEASE):
// - clicking toolbar buttons will cause the editor to lose focus
// - this is painful because we often want to know if the editor has focus
// so that we can insert images and so on in the correct place
// - the blur event will always fire before the button action is triggered 😞
// - to work around this we track focus state manually and set it to false
// after an arbitrary period that's long enough to allow the button action
// to trigger first
// - this _may_ well have unknown issues due to browser differences,
// variations in performance, moon cycles, sun spots, or cosmic rays
// - here be 🐲
// - (please let it work 🙏)
updateFocusState ( focused ) {
if ( focused ) {
this . _editorFocused = true ;
} else {
run . later ( this , function ( ) {
this . _editorFocused = false ;
} , 100 ) ;
}
} ,
openImageFileDialog ( ) {
let captureSelection = this . _editor . codemirror . hasFocus ( ) ;
this . _openImageFileDialog ( { captureSelection } ) ;
} ,
toggleUnsplash ( ) {
if ( this . get ( '_showUnsplash' ) ) {
return this . toggleProperty ( '_showUnsplash' ) ;
}
// capture current selection before it's lost by clicking toolbar btn
if ( this . _editorFocused ) {
this . _imageInsertSelection = {
anchor : this . _editor . codemirror . getCursor ( 'anchor' ) ,
head : this . _editor . codemirror . getCursor ( 'head' )
} ;
}
this . toggleProperty ( '_showUnsplash' ) ;
} ,
insertUnsplashPhoto ( photo ) {
let image = {
alt : photo . description || '' ,
url : photo . urls . regular ,
credit : ` <small>Photo by [ ${ photo . user . name } ]( ${ photo . user . links . html } ?utm_source=ghost&utm_medium=referral&utm_campaign=api-credit) / [Unsplash](https://unsplash.com/?utm_source=ghost&utm_medium=referral&utm_campaign=api-credit)</small> `
} ;
this . _insertImages ( [ image ] ) ;
} ,
togglePreview ( ) {
this . _togglePreview ( ) ;
} ,
toggleFullScreen ( ) {
let isFullScreen = ! this . get ( '_isFullScreen' ) ;
this . set ( '_isFullScreen' , isFullScreen ) ;
this . _updateButtonState ( ) ;
this . onFullScreenToggle ( isFullScreen ) ;
// leave split screen when exiting full screen mode
if ( ! isFullScreen && this . get ( '_isSplitScreen' ) ) {
this . send ( 'toggleSplitScreen' ) ;
}
} ,
toggleSplitScreen ( ) {
let isSplitScreen = ! this . get ( '_isSplitScreen' ) ;
let previewButton = this . _editor . toolbarElements . preview ;
this . set ( '_isSplitScreen' , isSplitScreen ) ;
this . _updateButtonState ( ) ;
// set up the preview rendering and scroll sync
// afterRender is needed so that necessary components have been
// added/removed and editor pane length has settled
if ( isSplitScreen ) {
// disable the normal SimpleMDE preview if it's active
if ( this . _editor . isPreviewActive ( ) ) {
let preview = this . _editor . toolbar . find ( button => button . name === 'preview' ) ;
preview . action ( this . _editor ) ;
}
if ( previewButton ) {
previewButton . classList . add ( 'disabled' ) ;
}
run . scheduleOnce ( 'afterRender' , this , this . _connectSplitPreview ) ;
} else {
if ( previewButton ) {
previewButton . classList . remove ( 'disabled' ) ;
}
run . scheduleOnce ( 'afterRender' , this , this . _disconnectSplitPreview ) ;
}
this . onSplitScreenToggle ( isSplitScreen ) ;
// go fullscreen when entering split screen mode
this . send ( 'toggleFullScreen' ) ;
} ,
toggleSpellcheck ( ) {
this . _toggleSpellcheck ( ) ;
} ,
toggleHemingway ( ) {
this . _toggleHemingway ( ) ;
} ,
toggleMarkdownHelp ( ) {
this . toggleProperty ( 'showMarkdownHelp' ) ;
} ,
// put the toolbar/statusbar elements back so that SimpleMDE doesn't throw
// errors when it tries to remove them
destroyEditor ( ) {
let container = this . $ ( '.gh-markdown-editor-pane' ) ;
this . _toolbar . appendTo ( container ) ;
this . _statusbar . appendTo ( container ) ;
this . _editor = null ;
}
} ,
2018-01-08 22:10:29 +03:00
_preventBodyScroll ( ) {
this . _preventBodyScrollId = window . requestAnimationFrame ( ( ) => {
let body = document . querySelector ( 'body' ) ;
// only scroll the editor if the editor is active so that we don't
// clobber scroll-to-input behaviour in the PSM
if ( document . activeElement . closest ( '.CodeMirror' ) ) {
if ( body . scrollTop !== 0 ) {
let editor = document . querySelector ( '.gh-markdown-editor' ) ;
// scroll the editor by the same amount the body has been scrolled,
// this should keep the cursor on screen when opening the keyboard
editor . scrollTop += body . scrollTop ;
body . scrollTop = 0 ;
}
}
this . _preventBodyScroll ( ) ;
} ) ;
2017-06-22 15:02:38 +03:00
} ,
2017-05-08 13:35:42 +03:00
_insertImages ( urls ) {
let cm = this . _editor . codemirror ;
// loop through urls and generate image markdown
let images = urls . map ( ( url ) => {
2017-08-02 10:05:59 +03:00
// plain url string, so extract filename from path
if ( typeOf ( url ) === 'string' ) {
let filename = url . split ( '/' ) . pop ( ) ;
let alt = filename ;
// if we have a normal filename.ext, set alt to filename -ext
if ( filename . lastIndexOf ( '.' ) > 0 ) {
alt = filename . slice ( 0 , filename . lastIndexOf ( '.' ) ) ;
}
2017-05-17 14:27:27 +03:00
2017-08-02 10:05:59 +03:00
return ` ![ ${ alt } ]( ${ url } ) ` ;
2017-05-17 14:27:27 +03:00
2017-08-02 10:05:59 +03:00
// full url object, use attrs we're given
} else {
let image = ` ![ ${ url . alt } ]( ${ url . url } ) ` ;
if ( url . credit ) {
image += ` \n ${ url . credit } ` ;
}
return image ;
}
2017-05-08 13:35:42 +03:00
} ) ;
2017-08-02 10:05:59 +03:00
let text = images . join ( '\n\n' ) ;
2017-05-08 13:35:42 +03:00
2017-05-10 18:16:36 +03:00
// clicking the image toolbar button will lose the selection so we use
// the captured selection to re-select here
if ( this . _imageInsertSelection ) {
// we want to focus but not re-position
this . send ( 'focusEditor' , null ) ;
// re-select and clear the captured selection so drag/drop still
// inserts at the correct place
cm . setSelection (
this . _imageInsertSelection . anchor ,
this . _imageInsertSelection . head
) ;
this . _imageInsertSelection = null ;
}
2017-05-08 13:35:42 +03:00
// focus editor and place cursor at end if not already focused
if ( ! cm . hasFocus ( ) ) {
this . send ( 'focusEditor' ) ;
2017-08-02 10:05:59 +03:00
text = ` \n \n ${ text } \n \n ` ;
2017-05-08 13:35:42 +03:00
}
// insert at cursor or replace selection then position cursor at end
// of inserted text
cm . replaceSelection ( text , 'end' ) ;
} ,
2017-05-18 13:08:50 +03:00
// mark the split-pane/full-screen/spellcheck buttons active when they're active
2017-05-08 21:15:56 +03:00
_updateButtonState ( ) {
if ( this . _editor ) {
let sideBySideButton = this . _editor . toolbarElements [ 'side-by-side' ] ;
2017-05-18 13:08:50 +03:00
let spellcheckButton = this . _editor . toolbarElements . spellcheck ;
2017-10-03 08:31:10 +03:00
let hemingwayButton = this . _editor . toolbarElements . hemingway ;
2017-05-08 21:15:56 +03:00
2017-07-20 14:37:18 +03:00
if ( sideBySideButton ) {
if ( this . get ( '_isSplitScreen' ) ) {
sideBySideButton . classList . add ( 'active' ) ;
} else {
sideBySideButton . classList . remove ( 'active' ) ;
}
2017-05-08 21:15:56 +03:00
}
2017-05-18 13:08:50 +03:00
2017-07-20 14:37:18 +03:00
if ( spellcheckButton ) {
if ( this . _editor . codemirror . getOption ( 'mode' ) === 'spell-checker' ) {
spellcheckButton . classList . add ( 'active' ) ;
} else {
spellcheckButton . classList . remove ( 'active' ) ;
}
2017-05-18 13:08:50 +03:00
}
2017-07-28 16:21:09 +03:00
2017-10-03 08:31:10 +03:00
if ( hemingwayButton ) {
if ( this . _isHemingwayMode ) {
hemingwayButton . classList . add ( 'active' ) ;
2017-07-28 16:21:09 +03:00
} else {
2017-10-03 08:31:10 +03:00
hemingwayButton . classList . remove ( 'active' ) ;
2017-07-28 16:21:09 +03:00
}
}
2017-05-08 21:15:56 +03:00
}
} ,
// set up the preview auto-update and scroll sync
_connectSplitPreview ( ) {
let cm = this . _editor . codemirror ;
let editor = this . _editor ;
let editorPane = this . $ ( '.gh-markdown-editor-pane' ) [ 0 ] ;
let previewPane = this . $ ( '.gh-markdown-editor-preview' ) [ 0 ] ;
let previewContent = this . $ ( '.gh-markdown-editor-preview-content' ) [ 0 ] ;
this . _editorPane = editorPane ;
this . _previewPane = previewPane ;
this . _previewContent = previewContent ;
// from SimpleMDE -------
2018-01-05 18:38:23 +03:00
let sideBySideRenderingFunction = function ( ) {
2017-05-08 21:15:56 +03:00
previewContent . innerHTML = editor . options . previewRender (
editor . value ( ) ,
previewContent
) ;
} ;
cm . sideBySideRenderingFunction = sideBySideRenderingFunction ;
sideBySideRenderingFunction ( ) ;
cm . on ( 'update' , cm . sideBySideRenderingFunction ) ;
// Refresh to fix selection being off (#309)
cm . refresh ( ) ;
// ----------------------
this . _onEditorPaneScroll = this . _scrollHandler . bind ( this ) ;
editorPane . addEventListener ( 'scroll' , this . _onEditorPaneScroll , false ) ;
this . _scrollSync ( ) ;
} ,
_scrollHandler ( ) {
if ( ! this . _scrollSyncTicking ) {
requestAnimationFrame ( this . _scrollSync . bind ( this ) ) ;
}
this . _scrollSyncTicking = true ;
} ,
_scrollSync ( ) {
let editorPane = this . _editorPane ;
let previewPane = this . _previewPane ;
let height = editorPane . scrollHeight - editorPane . clientHeight ;
let ratio = parseFloat ( editorPane . scrollTop ) / height ;
let move = ( previewPane . scrollHeight - previewPane . clientHeight ) * ratio ;
previewPane . scrollTop = move ;
this . _scrollSyncTicking = false ;
} ,
_disconnectSplitPreview ( ) {
let cm = this . _editor . codemirror ;
cm . off ( 'update' , cm . sideBySideRenderingFunction ) ;
cm . refresh ( ) ;
this . _editorPane . removeEventListener ( 'scroll' , this . _onEditorPaneScroll , false ) ;
delete this . _previewPane ;
delete this . _previewPaneContent ;
delete this . _onEditorPaneScroll ;
} ,
2017-07-04 13:11:54 +03:00
_openImageFileDialog ( { captureSelection = true } = { } ) {
2017-06-22 15:02:38 +03:00
if ( captureSelection ) {
// capture the current selection before it's lost by clicking the
// file input button
this . _imageInsertSelection = {
anchor : this . _editor . codemirror . getCursor ( 'anchor' ) ,
head : this . _editor . codemirror . getCursor ( 'head' )
} ;
}
2017-05-10 18:16:36 +03:00
// trigger the dialog via gh-file-input, when a file is selected it will
// trigger the onImageFilesSelected closure action
this . $ ( 'input[type="file"]' ) . click ( ) ;
} ,
2017-05-12 11:06:56 +03:00
// wrap SimpleMDE's built-in preview toggle so that we can trigger a closure
// action that can apply our own classes higher up in the DOM
_togglePreview ( ) {
this . onPreviewToggle ( ! this . _editor . isPreviewActive ( ) ) ;
this . _editor . togglePreview ( ) ;
} ,
2017-05-18 13:08:50 +03:00
_toggleSpellcheck ( ) {
let cm = this . _editor . codemirror ;
if ( cm . getOption ( 'mode' ) === 'spell-checker' ) {
2017-07-20 13:59:24 +03:00
cm . setOption ( 'mode' , 'gfm' ) ;
2017-05-18 13:08:50 +03:00
} else {
cm . setOption ( 'mode' , 'spell-checker' ) ;
}
this . _updateButtonState ( ) ;
} ,
2017-10-03 08:31:10 +03:00
_toggleHemingway ( ) {
2017-07-28 16:21:09 +03:00
let cm = this . _editor . codemirror ;
let extraKeys = cm . getOption ( 'extraKeys' ) ;
let notificationText = '' ;
2017-10-03 08:31:10 +03:00
this . _isHemingwayMode = ! this . _isHemingwayMode ;
2017-07-28 16:21:09 +03:00
2017-10-03 08:31:10 +03:00
if ( this . _isHemingwayMode ) {
2017-07-28 16:21:09 +03:00
notificationText = '<span class="gh-notification-title">Hemingway Mode On:</span> Write now; edit later. Backspace disabled.' ;
extraKeys . Backspace = function ( ) { } ;
} else {
notificationText = '<span class="gh-notification-title">Hemingway Mode Off:</span> Normal editing restored.' ;
delete extraKeys . Backspace ;
}
cm . setOption ( 'extraKeys' , extraKeys ) ;
this . _updateButtonState ( ) ;
cm . focus ( ) ;
this . get ( 'notifications' ) . showNotification (
htmlSafe ( notificationText ) ,
2017-10-03 08:31:10 +03:00
{ key : 'editor.hemingwaymode' }
2017-07-28 16:21:09 +03:00
) ;
2017-05-08 13:35:42 +03:00
}
} ) ;