summaryrefslogtreecommitdiff
path: root/public/js/lib/editor/index.js
diff options
context:
space:
mode:
Diffstat (limited to 'public/js/lib/editor/index.js')
-rw-r--r--public/js/lib/editor/index.js480
1 files changed, 480 insertions, 0 deletions
diff --git a/public/js/lib/editor/index.js b/public/js/lib/editor/index.js
new file mode 100644
index 00000000..6ae40d82
--- /dev/null
+++ b/public/js/lib/editor/index.js
@@ -0,0 +1,480 @@
+import * as utils from './utils'
+
+/* config section */
+const isMac = CodeMirror.keyMap.default === CodeMirror.keyMap.macDefault
+const defaultEditorMode = 'gfm'
+const viewportMargin = 20
+
+const jumpToAddressBarKeymapName = isMac ? 'Cmd-L' : 'Ctrl-L'
+
+export default class Editor {
+ constructor () {
+ this.editor = null
+ this.jumpToAddressBarKeymapValue = null
+ this.defaultExtraKeys = {
+ F10: function (cm) {
+ cm.setOption('fullScreen', !cm.getOption('fullScreen'))
+ },
+ Esc: function (cm) {
+ if (cm.getOption('keyMap').substr(0, 3) === 'vim') {
+ return CodeMirror.Pass
+ } else if (cm.getOption('fullScreen')) {
+ cm.setOption('fullScreen', false)
+ }
+ },
+ 'Cmd-S': function () {
+ return false
+ },
+ 'Ctrl-S': function () {
+ return false
+ },
+ Enter: 'newlineAndIndentContinueMarkdownList',
+ Tab: function (cm) {
+ var tab = '\t'
+
+ // contruct x length spaces
+ var spaces = Array(parseInt(cm.getOption('indentUnit')) + 1).join(' ')
+
+ // auto indent whole line when in list or blockquote
+ var cursor = cm.getCursor()
+ var line = cm.getLine(cursor.line)
+
+ // this regex match the following patterns
+ // 1. blockquote starts with "> " or ">>"
+ // 2. unorder list starts with *+-
+ // 3. order list starts with "1." or "1)"
+ var regex = /^(\s*)(>[> ]*|[*+-]\s|(\d+)([.)]))/
+
+ var match
+ var multiple = cm.getSelection().split('\n').length > 1 ||
+ cm.getSelections().length > 1
+
+ if (multiple) {
+ cm.execCommand('defaultTab')
+ } else if ((match = regex.exec(line)) !== null) {
+ var ch = match[1].length
+ var pos = {
+ line: cursor.line,
+ ch: ch
+ }
+ if (cm.getOption('indentWithTabs')) {
+ cm.replaceRange(tab, pos, pos, '+input')
+ } else {
+ cm.replaceRange(spaces, pos, pos, '+input')
+ }
+ } else {
+ if (cm.getOption('indentWithTabs')) {
+ cm.execCommand('defaultTab')
+ } else {
+ cm.replaceSelection(spaces)
+ }
+ }
+ },
+ 'Cmd-Left': 'goLineLeftSmart',
+ 'Cmd-Right': 'goLineRight',
+ 'Ctrl-C': function (cm) {
+ if (!isMac && cm.getOption('keyMap').substr(0, 3) === 'vim') {
+ document.execCommand('copy')
+ } else {
+ return CodeMirror.Pass
+ }
+ },
+ 'Ctrl-*': cm => {
+ utils.wrapTextWith(this.editor, cm, '*')
+ },
+ 'Shift-Ctrl-8': cm => {
+ utils.wrapTextWith(this.editor, cm, '*')
+ },
+ 'Ctrl-_': cm => {
+ utils.wrapTextWith(this.editor, cm, '_')
+ },
+ 'Shift-Ctrl--': cm => {
+ utils.wrapTextWith(this.editor, cm, '_')
+ },
+ 'Ctrl-~': cm => {
+ utils.wrapTextWith(this.editor, cm, '~')
+ },
+ 'Shift-Ctrl-`': cm => {
+ utils.wrapTextWith(this.editor, cm, '~')
+ },
+ 'Ctrl-^': cm => {
+ utils.wrapTextWith(this.editor, cm, '^')
+ },
+ 'Shift-Ctrl-6': cm => {
+ utils.wrapTextWith(this.editor, cm, '^')
+ },
+ 'Ctrl-+': cm => {
+ utils.wrapTextWith(this.editor, cm, '+')
+ },
+ 'Shift-Ctrl-=': cm => {
+ utils.wrapTextWith(this.editor, cm, '+')
+ },
+ 'Ctrl-=': cm => {
+ utils.wrapTextWith(this.editor, cm, '=')
+ },
+ 'Shift-Ctrl-Backspace': cm => {
+ utils.wrapTextWith(this.editor, cm, 'Backspace')
+ }
+ }
+ }
+
+ getStatusBarTemplate (callback) {
+ $.get(window.serverurl + '/views/statusbar.html', template => {
+ this.statusBarTemplate = template
+ if (callback) callback()
+ })
+ }
+
+ addStatusBar () {
+ if (!this.statusBarTemplate) {
+ this.getStatusBarTemplate(this.addStatusBar)
+ return
+ }
+ this.statusBar = $(this.statusBarTemplate)
+ this.statusCursor = this.statusBar.find('.status-cursor')
+ this.statusFile = this.statusBar.find('.status-file')
+ this.statusIndicators = this.statusBar.find('.status-indicators')
+ this.statusIndent = this.statusBar.find('.status-indent')
+ this.statusKeymap = this.statusBar.find('.status-keymap')
+ this.statusLength = this.statusBar.find('.status-length')
+ this.statusTheme = this.statusBar.find('.status-theme')
+ this.statusSpellcheck = this.statusBar.find('.status-spellcheck')
+ this.statusPreferences = this.statusBar.find('.status-preferences')
+ this.statusPanel = this.editor.addPanel(this.statusBar[0], {
+ position: 'bottom'
+ })
+
+ this.setIndent()
+ this.setKeymap()
+ this.setTheme()
+ this.setSpellcheck()
+ this.setPreferences()
+ }
+
+ setIndent () {
+ var cookieIndentType = Cookies.get('indent_type')
+ var cookieTabSize = parseInt(Cookies.get('tab_size'))
+ var cookieSpaceUnits = parseInt(Cookies.get('space_units'))
+ if (cookieIndentType) {
+ if (cookieIndentType === 'tab') {
+ this.editor.setOption('indentWithTabs', true)
+ if (cookieTabSize) {
+ this.editor.setOption('indentUnit', cookieTabSize)
+ }
+ } else if (cookieIndentType === 'space') {
+ this.editor.setOption('indentWithTabs', false)
+ if (cookieSpaceUnits) {
+ this.editor.setOption('indentUnit', cookieSpaceUnits)
+ }
+ }
+ }
+ if (cookieTabSize) {
+ this.editor.setOption('tabSize', cookieTabSize)
+ }
+
+ var type = this.statusIndicators.find('.indent-type')
+ var widthLabel = this.statusIndicators.find('.indent-width-label')
+ var widthInput = this.statusIndicators.find('.indent-width-input')
+
+ const setType = () => {
+ if (this.editor.getOption('indentWithTabs')) {
+ Cookies.set('indent_type', 'tab', {
+ expires: 365
+ })
+ type.text('Tab Size:')
+ } else {
+ Cookies.set('indent_type', 'space', {
+ expires: 365
+ })
+ type.text('Spaces:')
+ }
+ }
+ setType()
+
+ const setUnit = () => {
+ var unit = this.editor.getOption('indentUnit')
+ if (this.editor.getOption('indentWithTabs')) {
+ Cookies.set('tab_size', unit, {
+ expires: 365
+ })
+ } else {
+ Cookies.set('space_units', unit, {
+ expires: 365
+ })
+ }
+ widthLabel.text(unit)
+ }
+ setUnit()
+
+ type.click(() => {
+ if (this.editor.getOption('indentWithTabs')) {
+ this.editor.setOption('indentWithTabs', false)
+ cookieSpaceUnits = parseInt(Cookies.get('space_units'))
+ if (cookieSpaceUnits) {
+ this.editor.setOption('indentUnit', cookieSpaceUnits)
+ }
+ } else {
+ this.editor.setOption('indentWithTabs', true)
+ cookieTabSize = parseInt(Cookies.get('tab_size'))
+ if (cookieTabSize) {
+ this.editor.setOption('indentUnit', cookieTabSize)
+ this.editor.setOption('tabSize', cookieTabSize)
+ }
+ }
+ setType()
+ setUnit()
+ })
+ widthLabel.click(() => {
+ if (widthLabel.is(':visible')) {
+ widthLabel.addClass('hidden')
+ widthInput.removeClass('hidden')
+ widthInput.val(this.editor.getOption('indentUnit'))
+ widthInput.select()
+ } else {
+ widthLabel.removeClass('hidden')
+ widthInput.addClass('hidden')
+ }
+ })
+ widthInput.on('change', () => {
+ var val = parseInt(widthInput.val())
+ if (!val) val = this.editor.getOption('indentUnit')
+ if (val < 1) val = 1
+ else if (val > 10) val = 10
+
+ if (this.editor.getOption('indentWithTabs')) {
+ this.editor.setOption('tabSize', val)
+ }
+ this.editor.setOption('indentUnit', val)
+ setUnit()
+ })
+ widthInput.on('blur', function () {
+ widthLabel.removeClass('hidden')
+ widthInput.addClass('hidden')
+ })
+ }
+
+ setKeymap () {
+ var cookieKeymap = Cookies.get('keymap')
+ if (cookieKeymap) {
+ this.editor.setOption('keyMap', cookieKeymap)
+ }
+
+ var label = this.statusIndicators.find('.ui-keymap-label')
+ var sublime = this.statusIndicators.find('.ui-keymap-sublime')
+ var emacs = this.statusIndicators.find('.ui-keymap-emacs')
+ var vim = this.statusIndicators.find('.ui-keymap-vim')
+
+ const setKeymapLabel = () => {
+ var keymap = this.editor.getOption('keyMap')
+ Cookies.set('keymap', keymap, {
+ expires: 365
+ })
+ label.text(keymap)
+ this.restoreOverrideEditorKeymap()
+ this.setOverrideBrowserKeymap()
+ }
+ setKeymapLabel()
+
+ sublime.click(() => {
+ this.editor.setOption('keyMap', 'sublime')
+ setKeymapLabel()
+ })
+ emacs.click(() => {
+ this.editor.setOption('keyMap', 'emacs')
+ setKeymapLabel()
+ })
+ vim.click(() => {
+ this.editor.setOption('keyMap', 'vim')
+ setKeymapLabel()
+ })
+ }
+
+ setTheme () {
+ var cookieTheme = Cookies.get('theme')
+ if (cookieTheme) {
+ this.editor.setOption('theme', cookieTheme)
+ }
+
+ var themeToggle = this.statusTheme.find('.ui-theme-toggle')
+
+ const checkTheme = () => {
+ var theme = this.editor.getOption('theme')
+ if (theme === 'one-dark') {
+ themeToggle.removeClass('active')
+ } else {
+ themeToggle.addClass('active')
+ }
+ }
+
+ themeToggle.click(() => {
+ var theme = this.editor.getOption('theme')
+ if (theme === 'one-dark') {
+ theme = 'default'
+ } else {
+ theme = 'one-dark'
+ }
+ this.editor.setOption('theme', theme)
+ Cookies.set('theme', theme, {
+ expires: 365
+ })
+
+ checkTheme()
+ })
+
+ checkTheme()
+ }
+
+ setSpellcheck () {
+ var cookieSpellcheck = Cookies.get('spellcheck')
+ if (cookieSpellcheck) {
+ var mode = null
+ if (cookieSpellcheck === 'true' || cookieSpellcheck === true) {
+ mode = 'spell-checker'
+ } else {
+ mode = defaultEditorMode
+ }
+ if (mode && mode !== this.editor.getOption('mode')) {
+ this.editor.setOption('mode', mode)
+ }
+ }
+
+ var spellcheckToggle = this.statusSpellcheck.find('.ui-spellcheck-toggle')
+
+ const checkSpellcheck = () => {
+ var mode = this.editor.getOption('mode')
+ if (mode === defaultEditorMode) {
+ spellcheckToggle.removeClass('active')
+ } else {
+ spellcheckToggle.addClass('active')
+ }
+ }
+
+ spellcheckToggle.click(() => {
+ var mode = this.editor.getOption('mode')
+ if (mode === defaultEditorMode) {
+ mode = 'spell-checker'
+ } else {
+ mode = defaultEditorMode
+ }
+ if (mode && mode !== this.editor.getOption('mode')) {
+ this.editor.setOption('mode', mode)
+ }
+ Cookies.set('spellcheck', mode === 'spell-checker', {
+ expires: 365
+ })
+
+ checkSpellcheck()
+ })
+
+ checkSpellcheck()
+
+ // workaround spellcheck might not activate beacuse the ajax loading
+ if (window.num_loaded < 2) {
+ var spellcheckTimer = setInterval(
+ () => {
+ if (window.num_loaded >= 2) {
+ if (this.editor.getOption('mode') === 'spell-checker') {
+ this.editor.setOption('mode', 'spell-checker')
+ }
+ clearInterval(spellcheckTimer)
+ }
+ },
+ 100,
+ )
+ }
+ }
+
+ resetEditorKeymapToBrowserKeymap () {
+ var keymap = this.editor.getOption('keyMap')
+ if (!this.jumpToAddressBarKeymapValue) {
+ this.jumpToAddressBarKeymapValue = CodeMirror.keyMap[keymap][jumpToAddressBarKeymapName]
+ delete CodeMirror.keyMap[keymap][jumpToAddressBarKeymapName]
+ }
+ }
+
+ restoreOverrideEditorKeymap () {
+ var keymap = this.editor.getOption('keyMap')
+ if (this.jumpToAddressBarKeymapValue) {
+ CodeMirror.keyMap[keymap][jumpToAddressBarKeymapName] = this.jumpToAddressBarKeymapValue
+ this.jumpToAddressBarKeymapValue = null
+ }
+ }
+ setOverrideBrowserKeymap () {
+ var overrideBrowserKeymap = $(
+ '.ui-preferences-override-browser-keymap label > input[type="checkbox"]',
+ )
+ if (overrideBrowserKeymap.is(':checked')) {
+ Cookies.set('preferences-override-browser-keymap', true, {
+ expires: 365
+ })
+ this.restoreOverrideEditorKeymap()
+ } else {
+ Cookies.remove('preferences-override-browser-keymap')
+ this.resetEditorKeymapToBrowserKeymap()
+ }
+ }
+
+ setPreferences () {
+ var overrideBrowserKeymap = $(
+ '.ui-preferences-override-browser-keymap label > input[type="checkbox"]',
+ )
+ var cookieOverrideBrowserKeymap = Cookies.get(
+ 'preferences-override-browser-keymap',
+ )
+ if (cookieOverrideBrowserKeymap && cookieOverrideBrowserKeymap === 'true') {
+ overrideBrowserKeymap.prop('checked', true)
+ } else {
+ overrideBrowserKeymap.prop('checked', false)
+ }
+ this.setOverrideBrowserKeymap()
+
+ overrideBrowserKeymap.change(() => {
+ this.setOverrideBrowserKeymap()
+ })
+ }
+
+ init (textit) {
+ this.editor = CodeMirror.fromTextArea(textit, {
+ mode: defaultEditorMode,
+ backdrop: defaultEditorMode,
+ keyMap: 'sublime',
+ viewportMargin: viewportMargin,
+ styleActiveLine: true,
+ lineNumbers: true,
+ lineWrapping: true,
+ showCursorWhenSelecting: true,
+ highlightSelectionMatches: true,
+ indentUnit: 4,
+ continueComments: 'Enter',
+ theme: 'one-dark',
+ inputStyle: 'textarea',
+ matchBrackets: true,
+ autoCloseBrackets: true,
+ matchTags: {
+ bothTags: true
+ },
+ autoCloseTags: true,
+ foldGutter: true,
+ gutters: [
+ 'CodeMirror-linenumbers',
+ 'authorship-gutters',
+ 'CodeMirror-foldgutter'
+ ],
+ extraKeys: this.defaultExtraKeys,
+ flattenSpans: true,
+ addModeClass: true,
+ readOnly: true,
+ autoRefresh: true,
+ otherCursors: true,
+ placeholder: "← Start by entering a title here\n===\nVisit /features if you don't know what to do.\nHappy hacking :)"
+ })
+
+ this.getStatusBarTemplate()
+
+ return this.editor
+ }
+
+ getEditor () {
+ return this.editor
+ }
+}