summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYukai Huang2017-03-07 22:35:54 +0800
committerYukai Huang2017-03-08 21:43:32 +0800
commit6556c284e504790bd9ed0facac3322320e0c347b (patch)
tree6400dfbb9207c71aa3cd5d5c28a7c476f5c124b7
parent121d84863a055dbe259cdcfb98583a376bd8e2fa (diff)
Extract editor related code
- in public/js/lib/editor/index.js
-rw-r--r--public/js/index.js600
-rw-r--r--public/js/lib/editor/index.js459
-rw-r--r--public/js/lib/editor/utils.js46
3 files changed, 536 insertions, 569 deletions
diff --git a/public/js/index.js b/public/js/index.js
index f0c476ef..0d4da4d0 100644
--- a/public/js/index.js
+++ b/public/js/index.js
@@ -73,145 +73,12 @@ import {
var renderer = require('./render');
var preventXSS = renderer.preventXSS;
-var defaultTextHeight = 20;
-var viewportMargin = 20;
-var mac = CodeMirror.keyMap["default"] == CodeMirror.keyMap.macDefault;
-var defaultEditorMode = 'gfm';
-var 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';
- 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);
- 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 (!mac && cm.getOption('keyMap').substr(0, 3) === 'vim') document.execCommand("copy");
- else return CodeMirror.Pass;
- },
- "Ctrl-*": function (cm) {
- wrapTextWith(cm, '*');
- },
- "Shift-Ctrl-8": function (cm) {
- wrapTextWith(cm, '*');
- },
- "Ctrl-_": function (cm) {
- wrapTextWith(cm, '_');
- },
- "Shift-Ctrl--": function (cm) {
- wrapTextWith(cm, '_');
- },
- "Ctrl-~": function (cm) {
- wrapTextWith(cm, '~');
- },
- "Shift-Ctrl-`": function (cm) {
- wrapTextWith(cm, '~');
- },
- "Ctrl-^": function (cm) {
- wrapTextWith(cm, '^');
- },
- "Shift-Ctrl-6": function (cm) {
- wrapTextWith(cm, '^');
- },
- "Ctrl-+": function (cm) {
- wrapTextWith(cm, '+');
- },
- "Shift-Ctrl-=": function (cm) {
- wrapTextWith(cm, '+');
- },
- "Ctrl-=": function (cm) {
- wrapTextWith(cm, '=');
- },
- "Shift-Ctrl-Backspace": function (cm) {
- wrapTextWith(cm, 'Backspace');
- }
-};
+import Editor from './lib/editor';
-var wrapSymbols = ['*', '_', '~', '^', '+', '='];
+import getUIElements from './lib/editor/ui-elements';
-function wrapTextWith(cm, symbol) {
- if (!cm.getSelection()) {
- return CodeMirror.Pass;
- } else {
- var ranges = cm.listSelections();
- for (var i = 0; i < ranges.length; i++) {
- var range = ranges[i];
- if (!range.empty()) {
- var from = range.from(), to = range.to();
- if (symbol !== 'Backspace') {
- cm.replaceRange(symbol, to, to, '+input');
- cm.replaceRange(symbol, from, from, '+input');
- // workaround selection range not correct after add symbol
- var _ranges = cm.listSelections();
- var anchorIndex = editor.indexFromPos(_ranges[i].anchor);
- var headIndex = editor.indexFromPos(_ranges[i].head);
- if (anchorIndex > headIndex) {
- _ranges[i].anchor.ch--;
- } else {
- _ranges[i].head.ch--;
- }
- cm.setSelections(_ranges);
- } else {
- var preEndPos = {
- line: to.line,
- ch: to.ch + 1
- };
- var preText = cm.getRange(to, preEndPos);
- var preIndex = wrapSymbols.indexOf(preText);
- var postEndPos = {
- line: from.line,
- ch: from.ch - 1
- };
- var postText = cm.getRange(postEndPos, from);
- var postIndex = wrapSymbols.indexOf(postText);
- // check if surround symbol are list in array and matched
- if (preIndex > -1 && postIndex > -1 && preIndex === postIndex) {
- cm.replaceRange("", to, preEndPos, '+delete');
- cm.replaceRange("", postEndPos, from, '+delete');
- }
- }
- }
- }
- }
-}
+var defaultTextHeight = 20;
+var viewportMargin = 20;
var idleTime = 300000; //5 mins
var updateViewDebounce = 100;
@@ -432,353 +299,25 @@ window.fileTypes = {
"py": "python"
};
-//editor settings
+// editor settings
var textit = document.getElementById("textit");
-if (!textit) throw new Error("There was no textit area!");
-window.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: 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 :)"
-});
-var inlineAttach = inlineAttachment.editors.codemirror4.attach(editor);
-defaultTextHeight = parseInt($(".CodeMirror").css('line-height'));
-
-var statusBarTemplate = null;
-var statusBar = null;
-var statusPanel = null;
-var statusCursor = null;
-var statusFile = null;
-var statusIndicators = null;
-var statusLength = null;
-var statusKeymap = null;
-var statusIndent = null;
-var statusTheme = null;
-var statusSpellcheck = null;
-var statusPreferences = null;
-
-function getStatusBarTemplate(callback) {
- $.get(serverurl + '/views/statusbar.html', function (template) {
- statusBarTemplate = template;
- if (callback) callback();
- });
+if (!textit) {
+ throw new Error("There was no textit area!");
}
-getStatusBarTemplate();
-
-function addStatusBar() {
- if (!statusBarTemplate) {
- getStatusBarTemplate(addStatusBar);
- return;
- }
- statusBar = $(statusBarTemplate);
- statusCursor = statusBar.find('.status-cursor');
- statusFile = statusBar.find('.status-file');
- statusIndicators = statusBar.find('.status-indicators');
- statusIndent = statusBar.find('.status-indent');
- statusKeymap = statusBar.find('.status-keymap');
- statusLength = statusBar.find('.status-length');
- statusTheme = statusBar.find('.status-theme');
- statusSpellcheck = statusBar.find('.status-spellcheck');
- statusPreferences = statusBar.find('.status-preferences');
- statusPanel = editor.addPanel(statusBar[0], {
- position: "bottom"
- });
-
- setIndent();
- setKeymap();
- setTheme();
- setSpellcheck();
- setPreferences();
-}
-
-function 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') {
- editor.setOption('indentWithTabs', true);
- if (cookieTabSize)
- editor.setOption('indentUnit', cookieTabSize);
- } else if (cookieIndentType == 'space') {
- editor.setOption('indentWithTabs', false);
- if (cookieSpaceUnits)
- editor.setOption('indentUnit', cookieSpaceUnits);
- }
- }
- if (cookieTabSize)
- editor.setOption('tabSize', cookieTabSize);
-
- var type = statusIndicators.find('.indent-type');
- var widthLabel = statusIndicators.find('.indent-width-label');
- var widthInput = statusIndicators.find('.indent-width-input');
-
- function setType() {
- if (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();
- function setUnit() {
- var unit = editor.getOption('indentUnit');
- if (editor.getOption('indentWithTabs')) {
- Cookies.set('tab_size', unit, {
- expires: 365
- });
- } else {
- Cookies.set('space_units', unit, {
- expires: 365
- });
- }
- widthLabel.text(unit);
- }
- setUnit();
+const editorInstance = new Editor();
+var editor = editorInstance.init(textit);
- type.click(function () {
- if (editor.getOption('indentWithTabs')) {
- editor.setOption('indentWithTabs', false);
- cookieSpaceUnits = parseInt(Cookies.get('space_units'));
- if (cookieSpaceUnits)
- editor.setOption('indentUnit', cookieSpaceUnits)
- } else {
- editor.setOption('indentWithTabs', true);
- cookieTabSize = parseInt(Cookies.get('tab_size'));
- if (cookieTabSize) {
- editor.setOption('indentUnit', cookieTabSize);
- editor.setOption('tabSize', cookieTabSize);
- }
- }
- setType();
- setUnit();
- });
- widthLabel.click(function () {
- if (widthLabel.is(':visible')) {
- widthLabel.addClass('hidden');
- widthInput.removeClass('hidden');
- widthInput.val(editor.getOption('indentUnit'));
- widthInput.select();
- } else {
- widthLabel.removeClass('hidden');
- widthInput.addClass('hidden');
- }
- });
- widthInput.on('change', function () {
- var val = parseInt(widthInput.val());
- if (!val) val = editor.getOption('indentUnit');
- if (val < 1) val = 1;
- else if (val > 10) val = 10;
+// TODO: global referncing in jquery-textcomplete patch
+window.editor = editor;
- if (editor.getOption('indentWithTabs')) {
- editor.setOption('tabSize', val);
- }
- editor.setOption('indentUnit', val);
- setUnit();
- });
- widthInput.on('blur', function () {
- widthLabel.removeClass('hidden');
- widthInput.addClass('hidden');
- });
-}
-
-function setKeymap() {
- var cookieKeymap = Cookies.get('keymap');
- if (cookieKeymap)
- editor.setOption('keyMap', cookieKeymap);
-
- var label = statusIndicators.find('.ui-keymap-label');
- var sublime = statusIndicators.find('.ui-keymap-sublime');
- var emacs = statusIndicators.find('.ui-keymap-emacs');
- var vim = statusIndicators.find('.ui-keymap-vim');
-
- function setKeymapLabel() {
- var keymap = editor.getOption('keyMap');
- Cookies.set('keymap', keymap, {
- expires: 365
- });
- label.text(keymap);
- restoreOverrideEditorKeymap();
- setOverrideBrowserKeymap();
- }
- setKeymapLabel();
-
- sublime.click(function () {
- editor.setOption('keyMap', 'sublime');
- setKeymapLabel();
- });
- emacs.click(function () {
- editor.setOption('keyMap', 'emacs');
- setKeymapLabel();
- });
- vim.click(function () {
- editor.setOption('keyMap', 'vim');
- setKeymapLabel();
- });
-}
-
-function setTheme() {
- var cookieTheme = Cookies.get('theme');
- if (cookieTheme) {
- editor.setOption('theme', cookieTheme);
- }
-
- var themeToggle = statusTheme.find('.ui-theme-toggle');
- themeToggle.click(function () {
- var theme = editor.getOption('theme');
- if (theme == "one-dark") {
- theme = "default";
- } else {
- theme = "one-dark";
- }
- editor.setOption('theme', theme);
- Cookies.set('theme', theme, {
- expires: 365
- });
- checkTheme();
- });
- function checkTheme() {
- var theme = editor.getOption('theme');
- if (theme == "one-dark") {
- themeToggle.removeClass('active');
- } else {
- themeToggle.addClass('active');
- }
- }
- checkTheme();
-}
-
-function 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 !== editor.getOption('mode')) {
- editor.setOption('mode', mode);
- }
- }
-
- var spellcheckToggle = statusSpellcheck.find('.ui-spellcheck-toggle');
- spellcheckToggle.click(function () {
- var mode = editor.getOption('mode');
- if (mode == defaultEditorMode) {
- mode = "spell-checker";
- } else {
- mode = defaultEditorMode;
- }
- if (mode && mode !== editor.getOption('mode')) {
- editor.setOption('mode', mode);
- }
- Cookies.set('spellcheck', (mode == "spell-checker"), {
- expires: 365
- });
- checkSpellcheck();
- });
- function checkSpellcheck() {
- var mode = editor.getOption('mode');
- if (mode == defaultEditorMode) {
- spellcheckToggle.removeClass('active');
- } else {
- spellcheckToggle.addClass('active');
- }
- }
- checkSpellcheck();
-
- //workaround spellcheck might not activate beacuse the ajax loading
- if (num_loaded < 2) {
- var spellcheckTimer = setInterval(function () {
- if (num_loaded >= 2) {
- if (editor.getOption('mode') == "spell-checker")
- editor.setOption('mode', "spell-checker");
- clearInterval(spellcheckTimer);
- }
- }, 100);
- }
-}
-
-var jumpToAddressBarKeymapName = mac ? "Cmd-L" : "Ctrl-L";
-var jumpToAddressBarKeymapValue = null;
-function resetEditorKeymapToBrowserKeymap() {
- var keymap = editor.getOption('keyMap');
- if (!jumpToAddressBarKeymapValue) {
- jumpToAddressBarKeymapValue = CodeMirror.keyMap[keymap][jumpToAddressBarKeymapName];
- delete CodeMirror.keyMap[keymap][jumpToAddressBarKeymapName];
- }
-}
-function restoreOverrideEditorKeymap() {
- var keymap = editor.getOption('keyMap');
- if (jumpToAddressBarKeymapValue) {
- CodeMirror.keyMap[keymap][jumpToAddressBarKeymapName] = jumpToAddressBarKeymapValue;
- jumpToAddressBarKeymapValue = null;
- }
-}
-function 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
- });
- restoreOverrideEditorKeymap();
- } else {
- Cookies.remove('preferences-override-browser-keymap');
- resetEditorKeymapToBrowserKeymap();
- }
-}
-
-function 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);
- }
- setOverrideBrowserKeymap();
-
- overrideBrowserKeymap.change(function() {
- setOverrideBrowserKeymap();
- });
-}
+var inlineAttach = inlineAttachment.editors.codemirror4.attach(editor);
+defaultTextHeight = parseInt($(".CodeMirror").css('line-height'));
var selection = null;
function updateStatusBar() {
- if (!statusBar) return;
+ if (!editorInstance.statusBar) return;
var cursor = editor.getCursor();
var cursorText = 'Line ' + (cursor.line + 1) + ', Columns ' + (cursor.ch + 1);
if (selection) {
@@ -800,102 +339,25 @@ function updateStatusBar() {
if (start.line !== end.line || selectionCharCount > 0)
cursorText += selectionText;
}
- statusCursor.text(cursorText);
+ editorInstance.statusCursor.text(cursorText);
var fileText = ' — ' + editor.lineCount() + ' Lines';
- statusFile.text(fileText);
+ editorInstance.statusFile.text(fileText);
var docLength = editor.getValue().length;
- statusLength.text('Length ' + docLength);
+ editorInstance.statusLength.text('Length ' + docLength);
if (docLength > (docmaxlength * 0.95)) {
- statusLength.css('color', 'red');
- statusLength.attr('title', 'Your almost reach note max length limit.');
+ editorInstance.statusLength.css('color', 'red');
+ editorInstance.statusLength.attr('title', 'Your almost reach note max length limit.');
} else if (docLength > (docmaxlength * 0.8)) {
- statusLength.css('color', 'orange');
- statusLength.attr('title', 'You nearly fill the note, consider to make more pieces.');
+ editorInstance.statusLength.css('color', 'orange');
+ editorInstance.statusLength.attr('title', 'You nearly fill the note, consider to make more pieces.');
} else {
- statusLength.css('color', 'white');
- statusLength.attr('title', 'You could write up to ' + docmaxlength + ' characters in this note.');
- }
-}
-
-//ui vars
-window.ui = {
- spinner: $(".ui-spinner"),
- content: $(".ui-content"),
- toolbar: {
- shortStatus: $(".ui-short-status"),
- status: $(".ui-status"),
- new: $(".ui-new"),
- publish: $(".ui-publish"),
- extra: {
- revision: $(".ui-extra-revision"),
- slide: $(".ui-extra-slide")
- },
- download: {
- markdown: $(".ui-download-markdown"),
- html: $(".ui-download-html"),
- rawhtml: $(".ui-download-raw-html"),
- pdf: $(".ui-download-pdf-beta"),
- },
- export: {
- dropbox: $(".ui-save-dropbox"),
- googleDrive: $(".ui-save-google-drive"),
- gist: $(".ui-save-gist"),
- snippet: $(".ui-save-snippet")
- },
- import: {
- dropbox: $(".ui-import-dropbox"),
- googleDrive: $(".ui-import-google-drive"),
- gist: $(".ui-import-gist"),
- snippet: $(".ui-import-snippet"),
- clipboard: $(".ui-import-clipboard")
- },
- mode: $(".ui-mode"),
- edit: $(".ui-edit"),
- view: $(".ui-view"),
- both: $(".ui-both"),
- uploadImage: $(".ui-upload-image")
- },
- infobar: {
- lastchange: $(".ui-lastchange"),
- lastchangeuser: $(".ui-lastchangeuser"),
- nolastchangeuser: $(".ui-no-lastchangeuser"),
- permission: {
- permission: $(".ui-permission"),
- label: $(".ui-permission-label"),
- freely: $(".ui-permission-freely"),
- editable: $(".ui-permission-editable"),
- locked: $(".ui-permission-locked"),
- private: $(".ui-permission-private"),
- limited: $(".ui-permission-limited"),
- protected: $(".ui-permission-protected")
- },
- delete: $(".ui-delete-note")
- },
- toc: {
- toc: $('.ui-toc'),
- affix: $('.ui-affix-toc'),
- label: $('.ui-toc-label'),
- dropdown: $('.ui-toc-dropdown')
- },
- area: {
- edit: $(".ui-edit-area"),
- view: $(".ui-view-area"),
- codemirror: $(".ui-edit-area .CodeMirror"),
- codemirrorScroll: $(".ui-edit-area .CodeMirror .CodeMirror-scroll"),
- codemirrorSizer: $(".ui-edit-area .CodeMirror .CodeMirror-sizer"),
- codemirrorSizerInner: $(".ui-edit-area .CodeMirror .CodeMirror-sizer > div"),
- markdown: $(".ui-view-area .markdown-body"),
- resize: {
- handle: $('.ui-resizable-handle'),
- syncToggle: $('.ui-sync-toggle')
- }
- },
- modal: {
- snippetImportProjects: $("#snippetImportModalProjects"),
- snippetImportSnippets: $("#snippetImportModalSnippets"),
- revision: $("#revisionModal")
+ editorInstance.statusLength.css('color', 'white');
+ editorInstance.statusLength.attr('title', 'You could write up to ' + docmaxlength + ' characters in this note.');
}
-};
+}
+
+// initalize ui reference
+const ui = getUIElements();
//page actions
var opts = {
@@ -1146,7 +608,7 @@ var lastEditorWidth = 0;
var previousFocusOnEditor = null;
function checkEditorStyle() {
- var desireHeight = statusBar ? (ui.area.edit.height() - statusBar.outerHeight()) : ui.area.edit.height();
+ var desireHeight = editorInstance.statusBar ? (ui.area.edit.height() - editorInstance.statusBar.outerHeight()) : ui.area.edit.height();
// set editor height and min height based on scrollbar style and mode
var scrollbarStyle = editor.getOption('scrollbarStyle');
if (scrollbarStyle == 'overlay' || currentMode == modeType.both) {
@@ -1381,8 +843,8 @@ function changeMode(type) {
if (currentMode == modeType.edit || currentMode == modeType.both) {
ui.toolbar.uploadImage.fadeIn();
//add and update status bar
- if (!statusBar) {
- addStatusBar();
+ if (!editorInstance.statusBar) {
+ editorInstance.addStatusBar();
updateStatusBar();
}
//work around foldGutter might not init properly
@@ -4069,6 +3531,6 @@ $(editor.getInputField())
},
'textComplete:hide': function (e) {
$(this).data('autocompleting', false);
- editor.setOption("extraKeys", defaultExtraKeys);
+ editor.setOption("extraKeys", editorInstance.defaultExtraKeys);
}
});
diff --git a/public/js/lib/editor/index.js b/public/js/lib/editor/index.js
new file mode 100644
index 00000000..ec22ac92
--- /dev/null
+++ b/public/js/lib/editor/index.js
@@ -0,0 +1,459 @@
+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.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');
+ }
+ };
+
+ this.jumpToAddressBarKeymapValue = null;
+ }
+
+ 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;
+ }
+}
diff --git a/public/js/lib/editor/utils.js b/public/js/lib/editor/utils.js
new file mode 100644
index 00000000..120d3646
--- /dev/null
+++ b/public/js/lib/editor/utils.js
@@ -0,0 +1,46 @@
+const wrapSymbols = ['*', '_', '~', '^', '+', '='];
+export function wrapTextWith(editor, cm, symbol) {
+ if (!cm.getSelection()) {
+ return CodeMirror.Pass;
+ } else {
+ var ranges = cm.listSelections();
+ for (var i = 0; i < ranges.length; i++) {
+ var range = ranges[i];
+ if (!range.empty()) {
+ var from = range.from(), to = range.to();
+ if (symbol !== 'Backspace') {
+ cm.replaceRange(symbol, to, to, '+input');
+ cm.replaceRange(symbol, from, from, '+input');
+ // workaround selection range not correct after add symbol
+ var _ranges = cm.listSelections();
+ var anchorIndex = editor.indexFromPos(_ranges[i].anchor);
+ var headIndex = editor.indexFromPos(_ranges[i].head);
+ if (anchorIndex > headIndex) {
+ _ranges[i].anchor.ch--;
+ } else {
+ _ranges[i].head.ch--;
+ }
+ cm.setSelections(_ranges);
+ } else {
+ var preEndPos = {
+ line: to.line,
+ ch: to.ch + 1
+ };
+ var preText = cm.getRange(to, preEndPos);
+ var preIndex = wrapSymbols.indexOf(preText);
+ var postEndPos = {
+ line: from.line,
+ ch: from.ch - 1
+ };
+ var postText = cm.getRange(postEndPos, from);
+ var postIndex = wrapSymbols.indexOf(postText);
+ // check if surround symbol are list in array and matched
+ if (preIndex > -1 && postIndex > -1 && preIndex === postIndex) {
+ cm.replaceRange("", to, preEndPos, '+delete');
+ cm.replaceRange("", postEndPos, from, '+delete');
+ }
+ }
+ }
+ }
+ }
+}