Switch to ACE editor

This commit is contained in:
benweet 2013-09-13 00:25:25 +01:00
parent 12888d20e6
commit f5e80f2bbc
16 changed files with 242 additions and 136 deletions

View File

@ -14,7 +14,7 @@
<script>
// Use http://.../?debug to serve original JavaScript files instead of minified
var baseDir = 'res';
if(!location.search.match(/(\?|&)debug/)) {
if(!/(\?|&)debug($|&)/.test(location.search)) {
baseDir += '-min';
}
var require = {

View File

@ -1,10 +1,11 @@
define(function() {
function Extension(extensionId, extensionName, isOptional, disableInViewer) {
function Extension(extensionId, extensionName, isOptional, disableInViewer, disableInLight) {
this.extensionId = extensionId;
this.extensionName = extensionName;
this.isOptional = isOptional;
this.disableInViewer = disableInViewer;
this.disableInLight = disableInLight;
}
return Extension;

View File

@ -16,6 +16,7 @@ var BITLY_ACCESS_TOKEN = "317e033bfd48cf31155a68a536b1860013b09c4c";
var DEFAULT_FILE_TITLE = "Title";
var DEFAULT_FOLDER_NAME = "New folder";
var GDRIVE_DEFAULT_FILE_TITLE = "New Markdown document";
var EDITOR_DEFAULT_PADDING = 12;
var CHECK_ONLINE_PERIOD = 120000;
var AJAX_TIMEOUT = 30000;
var ASYNC_TASK_DEFAULT_TIMEOUT = 60000;

View File

@ -114,6 +114,8 @@ define([
utils.setInputValue("#input-settings-editor-font-family", settings.editorFontFamily);
// Editor font size
utils.setInputValue("#input-settings-editor-font-size", settings.editorFontSize);
// Editor max width
utils.setInputValue("#input-settings-editor-max-width", settings.editorMaxWidth);
// Default content
utils.setInputValue("#textarea-settings-default-content", settings.defaultContent);
// Commit message
@ -141,6 +143,8 @@ define([
newSettings.editorFontFamily = utils.getInputTextValue("#input-settings-editor-font-family", event);
// Editor font size
newSettings.editorFontSize = utils.getInputIntValue("#input-settings-editor-font-size", event, 1, 99);
// Editor max width
newSettings.editorMaxWidth = utils.getInputIntValue("#input-settings-editor-max-width", event, 1);
// Default content
newSettings.defaultContent = utils.getInputValue("#textarea-settings-default-content");
// Commit message
@ -190,14 +194,21 @@ define([
// Create ACE editor
var aceEditor = undefined;
function createAceEditor() {
if(lightMode) {
// In light mode, we replace ACE with a textarea
$('#wmd-input').replaceWith(function() {
return $('<textarea id="wmd-input">').addClass(this.className).addClass('form-control');
});
return;
}
aceEditor = ace.edit("wmd-input");
aceEditor.renderer.setShowGutter(false);
//aceEditor.renderer.setShowPrintMargin(false);
aceEditor.renderer.setShowPrintMargin(false);
aceEditor.renderer.setPrintMarginColumn(false);
aceEditor.renderer.setPadding(15);
aceEditor.renderer.setPadding(EDITOR_DEFAULT_PADDING);
aceEditor.session.setUseWrapMode(true);
aceEditor.session.setMode("libs/acemode");
// Make bold titles...
// Make titles bold...
(function(self) {
function customWorker() {
if (!self.running) { return; }
@ -273,9 +284,21 @@ define([
setPreviewButtonsVisibility(true);
}
},
onresize: function() {
aceEditor.resize();
eventMgr.onLayoutResize();
onresize_end: function(paneName) {
if(aceEditor !== undefined && paneName == 'center') {
aceEditor.resize();
setTimeout(function() {
var padding = (aceEditor.renderer.$size.scrollerWidth - settings.editorMaxWidth)/2;
if(padding < EDITOR_DEFAULT_PADDING) {
padding = EDITOR_DEFAULT_PADDING;
}
if(padding !== aceEditor.renderer.$padding) {
aceEditor.renderer.setPadding(padding);
aceEditor.resize(true);
}
}, 5);
}
eventMgr.onLayoutResize(paneName);
},
};
eventMgr.onLayoutConfigure(layoutGlobalConfig);
@ -285,7 +308,7 @@ define([
layout = $('body').layout($.extend(layoutGlobalConfig, {
east__resizable: true,
east__size: .5,
east__minSize: 250
east__minSize: 260
}));
}
else if(settings.layoutOrientation == "vertical") {
@ -308,14 +331,16 @@ define([
// have fixed position
// We also move the north toggler to the east or south resizer as the
// north resizer is very small
$previewButtonsElt = $('<div class="extension-preview-buttons">');
var $previewButtonsContainerElt = $('<div class="preview-button-container">');
$previewButtonsElt = $('<div class="extension-preview-buttons">').appendTo($previewButtonsContainerElt);
$editorButtonsElt = $('<div class="extension-editor-buttons">');
if(settings.layoutOrientation == "horizontal") {
$('.ui-layout-resizer-north').append($previewButtonsElt);
$('.ui-layout-resizer-north').append($previewButtonsContainerElt);
$('.ui-layout-resizer-east').append($northTogglerElt).append($editorButtonsElt);
}
else {
$('.ui-layout-resizer-south').append($previewButtonsElt).append($northTogglerElt).append($editorButtonsElt);
$previewButtonsContainerElt.append($editorButtonsElt);
$('.ui-layout-resizer-south').append($previewButtonsContainerElt).append($northTogglerElt);
}
setPanelVisibility();
@ -326,6 +351,7 @@ define([
// Create the PageDown editor
var editor = undefined;
var $editorElt = undefined;
var fileDesc = undefined;
var documentContent = undefined;
var UndoManager = require("ace/undomanager").UndoManager;
@ -337,40 +363,52 @@ define([
documentContent = undefined;
var initDocumentContent = fileDesc.content;
aceEditor.setValue(initDocumentContent, -1);
aceEditor.getSession().setUndoManager(new UndoManager());
if(aceEditor !== undefined) {
aceEditor.setValue(initDocumentContent, -1);
aceEditor.getSession().setUndoManager(new UndoManager());
}
else {
$editorElt.val(initDocumentContent);
}
if(editor !== undefined) {
// If the editor is already created
aceEditor.selection.setSelectionRange(fileDesc.editorSelectRange);
aceEditor.focus();
if(aceEditor !== undefined) {
aceEditor.selection.setSelectionRange(fileDesc.editorSelectRange);
aceEditor.focus();
}
else {
$editorElt.focus();
}
editor.refreshPreview();
return;
}
var $previewContainerElt = $(".preview-container");
// Store editor scrollTop on scroll event
var debouncedUpdateScroll = _.debounce(function() {
fileDesc.editorScrollTop = aceEditor.renderer.getScrollTop();
}, 100);
aceEditor.session.on('changeScrollTop', function() {
if(documentContent !== undefined) {
debouncedUpdateScroll();
}
});
// Store editor selection on change
aceEditor.session.selection.on('changeSelection', function() {
if(documentContent !== undefined) {
fileDesc.editorSelectRange = aceEditor.getSelectionRange();
}
});
// Store preview scrollTop on scroll event
$previewContainerElt.scroll(function() {
if(documentContent !== undefined) {
fileDesc.previewScrollTop = $previewContainerElt.scrollTop();
}
});
if(!lightMode) {
// Store editor scrollTop on scroll event
var debouncedUpdateScroll = _.debounce(function() {
fileDesc.editorScrollTop = aceEditor.renderer.getScrollTop();
}, 100);
aceEditor.session.on('changeScrollTop', function() {
if(documentContent !== undefined) {
debouncedUpdateScroll();
}
});
// Store editor selection on change
aceEditor.session.selection.on('changeSelection', function() {
if(documentContent !== undefined) {
fileDesc.editorSelectRange = aceEditor.getSelectionRange();
}
});
// Store preview scrollTop on scroll event
$previewContainerElt.scroll(function() {
if(documentContent !== undefined) {
fileDesc.previewScrollTop = $previewContainerElt.scrollTop();
}
});
}
// Create the converter and the editor
var converter = new Markdown.Converter();
@ -395,24 +433,35 @@ define([
eventMgr.onSectionsCreated(sectionList);
return text;
});
editor = new Markdown.Editor(converter);
// Custom insert link dialog
editor.hooks.set("insertLinkDialog", function(callback) {
core.insertLinkCallback = callback;
utils.resetModalInputs();
$(".modal-insert-link").modal();
return true;
});
// Custom insert image dialog
editor.hooks.set("insertImageDialog", function(callback) {
core.insertLinkCallback = callback;
if(core.catchModal) {
if(!lightMode) {
editor = new Markdown.Editor(converter);
// Custom insert link dialog
editor.hooks.set("insertLinkDialog", function(callback) {
core.insertLinkCallback = callback;
utils.resetModalInputs();
$(".modal-insert-link").modal();
return true;
}
utils.resetModalInputs();
$(".modal-insert-image").modal();
return true;
});
});
// Custom insert image dialog
editor.hooks.set("insertImageDialog", function(callback) {
core.insertLinkCallback = callback;
if(core.catchModal) {
return true;
}
utils.resetModalInputs();
$(".modal-insert-image").modal();
return true;
});
}
else {
$editor.on("input propertychange", function() {
});
editor = {
};
}
function checkDocumentChanges() {
var newDocumentContent = aceEditor.getValue();
@ -563,16 +612,17 @@ define([
}
});
// Editor's textarea
$("#wmd-input").css({
// ACE editor
createAceEditor();
// Editor's element
$editorElt = $("#wmd-input").css({
// Apply editor font
"font-family": settings.editorFontFamily,
"font-size": settings.editorFontSize + "px",
"line-height": Math.round(settings.editorFontSize * (20 / 12)) + "px"
});
// ACE editor
createAceEditor();
// UI layout
createLayout();

View File

@ -25,7 +25,7 @@ define([
"extensions/mathJax",
"extensions/emailConverter",
"extensions/scrollLink",
"extensions/focusMode",
"extensions/buttonFocusMode",
"extensions/buttonSync",
"extensions/buttonPublish",
"extensions/buttonShare",
@ -55,6 +55,10 @@ define([
// doesn't support it
extension.enabled = false;
}
else if(lightMode === true && extension.disableInLight === true) {
// Same for light mode
extension.enabled = false;
}
else {
// Enable the extension if it's not optional or it has not been
// disabled by the user

View File

@ -0,0 +1,60 @@
define([
"jquery",
"underscore",
"crel",
"classes/Extension"
], function($, _, crel, Extension) {
var buttonFocusMode = new Extension("buttonFocusMode", 'Button "Focus Mode"', true, true, true);
buttonFocusMode.settingsBlock = "When typing, scrolls automatically the editor to always have the caret centered verticaly.";
var aceEditor = undefined;
buttonFocusMode.onAceCreated = function(aceEditorParam) {
aceEditor = aceEditorParam;
};
var isFocusModeOn = false;
var isMouseActive = false;
function doFocusMode() {
if(isFocusModeOn === false || isMouseActive === true) {
return;
}
var positionInDocument = aceEditor.selection.getCursor();
var positionInScreen = aceEditor.session.documentToScreenPosition(positionInDocument.row, positionInDocument.column);
aceEditor.session.setScrollTop((positionInScreen.row + 0.5) * aceEditor.renderer.lineHeight - aceEditor.renderer.$size.scrollerHeight / 2);
}
var $button = undefined;
buttonFocusMode.onReady = function() {
aceEditor.getSession().selection.on('changeCursor', doFocusMode);
aceEditor.container.addEventListener('keydown', function() {
isMouseActive = false;
}, true);
aceEditor.container.addEventListener('mousedown', function() {
isMouseActive = true;
}, true);
if(localStorage.focusMode == 'on') {
$button.click();
}
};
buttonFocusMode.onCreateEditorButton = function() {
$button = $([
'<button class="btn btn-info" title="Focus Mode" data-toggle="button">',
' <i class="icon-target"></i>',
'</button>'
].join(''));
$button.click(function() {
_.defer(function() {
isFocusModeOn = $button.is('.active');
localStorage.focusMode = isFocusModeOn ? 'on' : 'off';
isMouseActive = false;
aceEditor.focus();
doFocusMode();
});
});
return $button[0];
};
return buttonFocusMode;
});

View File

@ -52,6 +52,7 @@ define([
var sortFunction = undefined;
var selectFileDesc = undefined;
var selectedLi = undefined;
var $editorElt = undefined;
var buildSelector = function() {
var liListHtml = _.chain(fileSystem).sortBy(sortFunction).reduce(function(result, fileDesc) {
return result + _.template(liEltTmpl, {
@ -73,9 +74,12 @@ define([
if(!$liElt.hasClass("disabled")) {
fileMgr.selectFile(fileDesc);
}
else {
else if(aceEditor !== undefined) {
aceEditor.focus();
}
else {
$editorElt.focus();
}
});
});
@ -95,6 +99,8 @@ define([
documentSelector.onPublishRemoved = buildSelector;
documentSelector.onReady = function() {
$editorElt = $('#wmd-input');
if(documentSelector.config.orderBy == "title") {
sortFunction = function(fileDesc) {
return fileDesc.title.toLowerCase();

View File

@ -1,48 +0,0 @@
define([
"jquery",
"underscore",
"crel",
"classes/Extension"
], function($, _, crel, Extension) {
var focusMode = new Extension("focusMode", "Focus Mode", true, true);
focusMode.settingsBlock = "Scrolls automatically the editor to have the caret verticaly centered";
var aceEditor = undefined;
focusMode.onAceCreated = function(aceEditorParam) {
aceEditor = aceEditorParam;
};
var isActive = false;
function doFocus() {
if(isActive === false) {
return;
}
var positionInDocument = aceEditor.selection.getCursor();
var positionInScreen = aceEditor.session.documentToScreenPosition(positionInDocument.row, positionInDocument.column);
aceEditor.session.setScrollTop((positionInScreen.row+0.5) * aceEditor.renderer.lineHeight - aceEditor.renderer.$size.scrollerHeight / 2);
}
focusMode.onReady = function() {
//aceEditor.getSession().on('change', doFocus);
aceEditor.getSession().selection.on('changeCursor', doFocus);
};
focusMode.onCreateEditorButton = function() {
var $button = $([
'<button class="btn btn-info" title="Focus Mode" data-toggle="button">',
' <i class="icon-target"></i>',
'</button>'
].join(''));
$button.click(function() {
_.defer(function() {
isActive = $button.is('.active');
aceEditor.focus();
doFocus();
});
});
return $button[0];
};
return focusMode;
});

View File

@ -5,7 +5,7 @@ define([
"text!html/scrollLinkSettingsBlock.html"
], function($, _, Extension, scrollLinkSettingsBlockHTML) {
var scrollLink = new Extension("scrollLink", "Scroll Link", true, true);
var scrollLink = new Extension("scrollLink", "Scroll Link", true, true, true);
scrollLink.settingsBlock = scrollLinkSettingsBlockHTML;
var aceEditor = undefined;

View File

@ -34,7 +34,7 @@ define([
tour.addSteps([
{
element: ".navbar-inner",
title: "Welcome to StackEdit 2.0",
title: "Welcome to StackEdit",
content: "Please click <code>Next</code> to take a quick tour.",
placement: "bottom",
},
@ -42,7 +42,7 @@ define([
element: ".navbar .action-create-file",
title: "New document",
content: "Click the <i class='icon-file'></i> <code>New document</code> button to create a new document.",
placement: "left",
placement: "bottom",
reflex: true,
},
{

View File

@ -944,6 +944,15 @@
class="form-control col-lg-2"> px
</div>
</div>
<div class="form-group">
<label class="col-lg-4 control-label"
for="input-settings-editor-max-width">Editor max width</label>
<div class="col-lg-8 form-inline">
<input type="text"
id="input-settings-editor-max-width"
class="form-control col-lg-3"> px
</div>
</div>
<div class="form-group">
<label class="col-lg-4 control-label"
for="textarea-settings-default-content">Default content

View File

@ -120,12 +120,25 @@ var logger = {
}
};
// We can run StackEdit with http://.../?console to print logs in the console
if(location.search.match(/(\?|&)console/)) {
if(/(\?|&)console($|&)/.test(location.search)) {
logger = console;
}
// Viewer mode is deduced from the body class
var viewerMode = /(^| )viewer($| )/.test(document.body.className);
// Light mode is for mobile or viewer
var lightMode =
viewerMode
|| /(\?|&)light($|&)/.test(location.search)
|| (function(a) {
if(/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i.test(a)
|| /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i
.test(a.substr(0, 4)))
return true;
})(navigator.userAgent || navigator.vendor || window.opera);
// Keep the theme in a global variable
var theme = localStorage.theme || 'default';
var themeModule = "less!themes/" + theme;
if(baseDir.indexOf('-min') !== -1) {

View File

@ -8,6 +8,7 @@ define([
lazyRendering: true,
editorFontFamily: 'Menlo, Consolas, "Courier New", Courier, monospace',
editorFontSize: 12,
editorMaxWidth: 960,
defaultContent: "\n\n\n> Written with [StackEdit](" + MAIN_URL + ").",
commitMsg: "Published with " + MAIN_URL,
template: [

View File

@ -561,15 +561,23 @@ body {
/********************
* Preview extensions buttons
* Preview/Editor extensions buttons
********************/
.preview-button-container {
position: absolute;
right: 0;
.extension-editor-buttons {
position: relative;
}
}
.extension-preview-buttons {
position: absolute;
right: 30px;
display: inline-block;
z-index: 1;
top: 6px;
.ui-layout-resizer-south-closed > & {
margin-top: 6px;
margin-right: 30px;
.ui-layout-resizer-south-closed & {
display: none !important;
}
.dropdown-menu {
@ -580,7 +588,7 @@ body {
margin: 0 0 0 1px;
.btn {
position: initial;
background-color: fade(@navbar-default-bg, 50%);
background-color: fade(@navbar-default-bg, 33%);
}
&.open .btn{
background-color: @primary-bg-light;
@ -617,25 +625,22 @@ body {
}
}
/********************
* Editor extensions buttons
********************/
.extension-editor-buttons {
position: absolute;
display: inline-block;
vertical-align: top;
.btn {
background-color: fade(@navbar-default-bg, 33%);
text-align: center;
height: 35px;
width: 35px;
padding: 0;
}
.ui-layout-resizer-east & {
bottom: 0;
& .btn {
width: 35px;
padding: 10px 7px;
}
}
.ui-layout-resizer-south & {
right: 0;
& .btn {
height: 35px;
}
}
}
@ -765,7 +770,7 @@ body {
&:focus,
&:active,
&.active {
background-color: fade(@navbar-default-bg, 50%);
background-color: mix(@primary-bg-lighter, @navbar-default-bg, 50%);
i {
color: @btn-success-color
}
@ -945,7 +950,7 @@ ul,ol {
}
.ace_markup.ace_heading {
color: @primary-color-light;
color: @primary-color;
font-weight: bold;
}
@ -958,6 +963,7 @@ ul,ol {
}
.ace_emphasis {
color: @primary-color;
font-style: italic;
}
@ -981,10 +987,13 @@ ul,ol {
}
#wmd-input {
color: @primary-color-light;
.box-shadow(none);
padding: 0;
resize: none;
border: none !important;
div& {
padding: 0;
}
}
.preview-container {
@ -1169,7 +1178,7 @@ input[type="file"] {
*********************/
.popover {
max-width: 360px;
max-width: 350px;
padding: 15px;
.box-shadow(0 5px 30px rgba(0,0,0,.4));
.popover-title {

View File

@ -72,7 +72,7 @@ define([
return undefined;
}
value = parseInt(value);
if((value === NaN) || (min !== undefined && value < min) || (max !== undefined && value > max)) {
if(isNaN(value) || (min !== undefined && value < min) || (max !== undefined && value > max)) {
inputError(element, event);
return undefined;
}

View File

@ -15,7 +15,7 @@
<script>
// Use http://.../?debug to serve original JavaScript files instead of minified
var baseDir = 'res';
if(!location.search.match(/(\?|&)debug/)) {
if(!/(\?|&)debug($|&)/.test(location.search)) {
baseDir += '-min';
}
var require = {