/*globals Markdown, requirejs */ define([ "jquery", "underscore", "crel", "ace", "constants", "utils", "storage", "settings", "eventMgr", "shortcutMgr", "mousetrap", "text!html/bodyIndex.html", "text!html/bodyViewer.html", "text!html/settingsTemplateTooltip.html", "text!html/settingsUserCustomExtensionTooltip.html", "storage", "uilayout", 'pagedown-ace', 'pagedown-light', 'libs/ace_mode', 'ace/requirejs/text!ace/css/editor.css', 'ace/requirejs/text!ace/theme/textmate.css', 'ace/ext/spellcheck', 'ace/ext/searchbox' ], function($, _, crel, ace, constants, utils, storage, settings, eventMgr, shortcutMgr, mousetrap, bodyIndexHTML, bodyViewerHTML, settingsTemplateTooltipHTML, settingsUserCustomExtensionTooltipHTML) { var core = {}; // Used for periodic tasks var intervalId; // Used to detect user activity var isUserReal = false; var userActive = false; var windowUnique = true; var userLastActivity = 0; function setUserActive() { isUserReal = true; userActive = true; var currentTime = utils.currentTime; if(currentTime > userLastActivity + 1000) { userLastActivity = currentTime; eventMgr.onUserActive(); } } function isUserActive() { if(utils.currentTime - userLastActivity > constants.USER_IDLE_THRESHOLD) { userActive = false; } return userActive && windowUnique; } // Used to only have 1 window of the application in the same browser var windowId; function checkWindowUnique() { if(isUserReal === false || windowUnique === false) { return; } if(windowId === undefined) { windowId = utils.randomString(); storage.frontWindowId = windowId; } var frontWindowId = storage.frontWindowId; if(frontWindowId != windowId) { windowUnique = false; if(intervalId !== undefined) { clearInterval(intervalId); } $(".modal").modal("hide"); $('.modal-non-unique').modal("show"); // Attempt to close the window window.close(); } } // Offline management var isOffline = false; var offlineTime = utils.currentTime; core.setOffline = function() { offlineTime = utils.currentTime; if(isOffline === false) { isOffline = true; eventMgr.onOfflineChanged(true); } }; function setOnline() { if(isOffline === true) { isOffline = false; eventMgr.onOfflineChanged(false); } } function checkOnline() { // Try to reconnect if we are offline but we have some network if(isOffline === true && navigator.onLine === true && offlineTime + constants.CHECK_ONLINE_PERIOD < utils.currentTime) { offlineTime = utils.currentTime; // Try to download anything to test the connection $.ajax({ url: "//www.google.com/jsapi", timeout: constants.AJAX_TIMEOUT, dataType: "script" }).done(function() { setOnline(); }); } } // Load settings in settings dialog var $themeInputElt; function loadSettings() { // Layout orientation utils.setInputRadio("radio-layout-orientation", settings.layoutOrientation); // Theme utils.setInputValue($themeInputElt, window.theme); $themeInputElt.change(); // Lazy rendering utils.setInputChecked("#input-settings-lazy-rendering", settings.lazyRendering); // Editor font family utils.setInputValue("#input-settings-editor-font-family", settings.editorFontFamily); // Editor font size utils.setInputValue("#input-settings-editor-font-size", settings.editorFontSize); // Max width utils.setInputValue("#input-settings-max-width", settings.maxWidth); // Default content utils.setInputValue("#textarea-settings-default-content", settings.defaultContent); // Mode utils.setInputRadio("radio-settings-mode", storage.mode || '_ace_'); // Commit message utils.setInputValue("#input-settings-publish-commit-msg", settings.commitMsg); // Gdrive multi-accounts utils.setInputValue("#input-settings-gdrive-multiaccount", settings.gdriveMultiAccount); // Gdrive full access utils.setInputChecked("#input-settings-gdrive-full-access", settings.gdriveFullAccess); // Dropbox full access utils.setInputChecked("#input-settings-dropbox-full-access", settings.dropboxFullAccess); // Template utils.setInputValue("#textarea-settings-publish-template", settings.template); // PDF template utils.setInputValue("#textarea-settings-pdf-template", settings.pdfTemplate); // PDF page size utils.setInputValue("#input-settings-pdf-page-size", settings.pdfPageSize); // SSH proxy utils.setInputValue("#input-settings-ssh-proxy", settings.sshProxy); // Load shortcuts settings shortcutMgr.loadSettings(); // Load extension settings eventMgr.onLoadSettings(); } // Save settings from settings dialog function saveSettings(event) { var newSettings = {}; // Layout orientation newSettings.layoutOrientation = utils.getInputRadio("radio-layout-orientation"); // Theme var theme = utils.getInputValue($themeInputElt); // Lazy Rendering newSettings.lazyRendering = utils.getInputChecked("#input-settings-lazy-rendering"); // Editor font family 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); // Max width newSettings.maxWidth = utils.getInputIntValue("#input-settings-max-width", event, 1); // Default content newSettings.defaultContent = utils.getInputValue("#textarea-settings-default-content"); // Mode var mode = utils.getInputRadio("radio-settings-mode"); // Commit message newSettings.commitMsg = utils.getInputTextValue("#input-settings-publish-commit-msg", event); // Gdrive multi-accounts newSettings.gdriveMultiAccount = utils.getInputIntValue("#input-settings-gdrive-multiaccount"); // Gdrive full access newSettings.gdriveFullAccess = utils.getInputChecked("#input-settings-gdrive-full-access"); // Drobox full access newSettings.dropboxFullAccess = utils.getInputChecked("#input-settings-dropbox-full-access"); // Template newSettings.template = utils.getInputTextValue("#textarea-settings-publish-template", event); // PDF template newSettings.pdfTemplate = utils.getInputTextValue("#textarea-settings-pdf-template", event); // PDF page size newSettings.pdfPageSize = utils.getInputValue("#input-settings-pdf-page-size"); // SSH proxy newSettings.sshProxy = utils.checkUrl(utils.getInputTextValue("#input-settings-ssh-proxy", event), true); // Save shortcuts settings shortcutMgr.saveSettings(newSettings); // Save extension settings newSettings.extensionSettings = {}; eventMgr.onSaveSettings(newSettings.extensionSettings, event); if(!event.isPropagationStopped()) { if(settings.dropboxFullAccess !== newSettings.dropboxFullAccess) { storage.removeItem('dropbox.lastChangeId'); } $.extend(settings, newSettings); storage.settings = JSON.stringify(settings); storage.themeV3 = theme; storage.mode = mode; } } // Set the panels visibility var layout; var $menuPanelElt; var $documentPanelElt; function setPanelVisibility(forceHide) { if(forceHide === true || layout.state.north.isClosed) { $menuPanelElt.hide(); $documentPanelElt.hide(); } else { $menuPanelElt.show(); $documentPanelElt.show(); } } // Set the preview button visibility var $previewButtonsElt; function setPreviewButtonsVisibility(forceHide) { if(forceHide === true || layout.state.east.isClosed) { $previewButtonsElt.hide(); } else { $previewButtonsElt.show(); } } // Create ACE editor var aceEditor; function createAceEditor() { aceEditor = ace.edit("wmd-input"); aceEditor.setOption("spellcheck", true); aceEditor.renderer.setShowGutter(false); aceEditor.renderer.setPrintMarginColumn(false); aceEditor.renderer.setPadding(constants.EDITOR_DEFAULT_PADDING); aceEditor.session.setUseWrapMode(true); aceEditor.session.setNewLineMode("unix"); aceEditor.session.setMode("libs/ace_mode"); aceEditor.session.$selectLongWords = true; // Make bold titles... (function(self) { function checkLine(currentLine) { var line = self.lines[currentLine]; if(line.length !== 0) { if(line[0].type.indexOf("markup.heading.multi") === 0) { _.each(self.lines[currentLine - 1], function(previousLineObject) { previousLineObject.type = "markup.heading.prev.multi"; }); } } } function customWorker() { // Duplicate from background_tokenizer.js if(!self.running) { return; } var workerStart = new Date(); var currentLine = self.currentLine; var endLine = -1; var doc = self.doc; while (self.lines[currentLine]) { currentLine++; } var startLine = currentLine; var len = doc.getLength(); var processedLines = 0; self.running = false; while (currentLine < len) { self.$tokenizeRow(currentLine); endLine = currentLine; do { checkLine(currentLine); // benweet currentLine++; } while (self.lines[currentLine]); // only check every 5 lines processedLines++; if((processedLines % 5 === 0) && (new Date() - workerStart) > 20) { self.running = setTimeout(customWorker, 20); // benweet self.currentLine = currentLine; return; } } self.currentLine = currentLine; if(startLine <= endLine) { self.fireUpdateEvent(startLine, endLine); } } self.$worker = function() { self.lines.splice(0, self.lines.length); self.states.splice(0, self.states.length); self.currentLine = 0; customWorker(); }; })(aceEditor.session.bgTokenizer); shortcutMgr.configureAce(aceEditor); eventMgr.onAceCreated(aceEditor); } // Create the layout var $editorButtonsElt; function createLayout() { var layoutGlobalConfig = { closable: true, resizable: false, slidable: false, livePaneResizing: true, enableCursorHotkey: false, resizerDblClickToggle: false, resizeWithWindow: false, north__spacing_open: 1, north__spacing_closed: 1, spacing_open: 35, spacing_closed: 35, togglerLength_open: 60, togglerLength_closed: 60, stateManagement__enabled: false, north__minSize: 49, center__minWidth: 250, center__minHeight: 180, fxSettings: { easing: "easeInOutQuad", duration: 350 }, onopen: function() { setPanelVisibility(); setPreviewButtonsVisibility(); }, onclose_start: function(paneName) { if(paneName == 'north') { setPanelVisibility(true); } else if(paneName == 'east') { setPreviewButtonsVisibility(true); } }, onresize_end: function(paneName) { if(aceEditor !== undefined && paneName == 'center') { aceEditor.resize(); var bottomMargin = (aceEditor.renderer.$size.scrollerHeight - aceEditor.renderer.lineHeight) / 2; bottomMargin < 0 && (bottomMargin = 0); aceEditor.renderer.setScrollMargin(0, bottomMargin, 0, 0); setTimeout(function() { var padding = (aceEditor.renderer.$size.scrollerWidth - settings.maxWidth) / 2; if(padding < constants.EDITOR_DEFAULT_PADDING) { padding = constants.EDITOR_DEFAULT_PADDING; } if(padding !== aceEditor.renderer.$padding) { aceEditor.renderer.setPadding(padding); aceEditor.resize(true); } }, 5); } eventMgr.onLayoutResize(paneName); }, }; eventMgr.onLayoutConfigure(layoutGlobalConfig); if(settings.layoutOrientation == "horizontal") { $(".ui-layout-south").remove(); $(".preview-container").html('
'); layout = $('body').layout($.extend(layoutGlobalConfig, { east__resizable: true, east__size: 0.5, east__minSize: 300 })); } else if(settings.layoutOrientation == "vertical") { $(".ui-layout-east").remove(); $(".preview-container").html('
'); layout = $('body').layout($.extend(layoutGlobalConfig, { south__resizable: true, south__size: 0.5, south__minSize: 200 })); } settings.maxWidth && $('#preview-contents').css('max-width', (settings.maxWidth + 30) + 'px'); $(".navbar").click(function() { layout.allowOverflow('north'); }); $(".ui-layout-toggler-south").addClass("btn btn-info").html(''); $(".ui-layout-toggler-east").addClass("btn btn-info").html(''); var $northTogglerElt = $(".ui-layout-toggler-north").addClass("btn btn-info").html(''); // We attach the preview buttons to the UI layout resizer in order to // have fixed position // We also move the north toggler to the east or south resizer as the // north resizer is very small // var $previewButtonsContainerElt = $('
'); var $resizerDecorator = $('
'); $previewButtonsElt = $('
'); $editorButtonsElt = $('
'); if(window.viewerMode || settings.layoutOrientation == "horizontal") { $('.ui-layout-resizer-north').append($previewButtonsElt); $('.ui-layout-resizer-east').append($resizerDecorator).append($northTogglerElt).append($editorButtonsElt); } else { $('.ui-layout-resizer-south').append($resizerDecorator).append($previewButtonsElt).append($editorButtonsElt).append($northTogglerElt); } setPanelVisibility(); setPreviewButtonsVisibility(); eventMgr.onLayoutCreated(layout); } var $navbarElt; var $leftBtnElts; var $rightBtnElts; var $leftBtnDropdown; var $rightBtnDropdown; var marginWidth = 36 + 25 + 25; var titleWidth = 18 + 348; var leftButtonsWidth = 72 + 83 + 166 + 167 + 83; var rightButtonsWidth = 36 + 84 + 83; var rightButtonsDropdown = 42; function adjustWindow() { if(!window.viewerMode) { var maxWidth = $navbarElt.width() - 5; if(marginWidth + titleWidth + leftButtonsWidth + rightButtonsWidth > maxWidth) { $rightBtnDropdown.show().find('.dropdown-menu').append($rightBtnElts); if(marginWidth + titleWidth + leftButtonsWidth + rightButtonsDropdown > maxWidth) { $leftBtnDropdown.show().find('.dropdown-menu').append($leftBtnElts); } else { $leftBtnDropdown.hide().after($leftBtnElts); } } else { $leftBtnDropdown.hide().after($leftBtnElts); $rightBtnDropdown.hide().after($rightBtnElts); } } layout.resizeAll(); } // Create the PageDown editor var editor; var $editorElt; var fileDesc; var documentContent; var UndoManager = require("ace/undomanager").UndoManager; core.initEditor = function(fileDescParam) { if(fileDesc !== undefined) { eventMgr.onFileClosed(fileDesc); } fileDesc = fileDescParam; documentContent = undefined; var initDocumentContent = fileDesc.content; 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 && aceEditor.selection.setSelectionRange(fileDesc.editorSelectRange); aceEditor ? aceEditor.focus() : $editorElt.focus(); editor.refreshPreview(); return; } var $previewContainerElt = $(".preview-container"); if(window.lightMode) { // Store editor scrollTop on scroll event $editorElt.scroll(function() { if(documentContent !== undefined) { fileDesc.editorScrollTop = $(this).scrollTop(); } }); // Store editor selection on change $editorElt.bind("keyup mouseup", function() { if(documentContent !== undefined) { fileDesc.editorStart = this.selectionStart; fileDesc.editorEnd = this.selectionEnd; } }); } else { // Store editor scrollTop on scroll event var saveScroll = _.debounce(function() { if(documentContent !== undefined) { fileDesc.editorScrollTop = aceEditor.renderer.getScrollTop(); } }, 100); aceEditor.session.on('changeScrollTop', saveScroll); // Store editor selection on change var saveSelection = _.debounce(function() { if(documentContent !== undefined) { fileDesc.editorSelectRange = aceEditor.getSelectionRange(); } }, 100); aceEditor.session.selection.on('changeSelection', saveSelection); aceEditor.session.selection.on('changeCursor', saveSelection); } // 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(); function checkDocumentChanges() { var newDocumentContent = $editorElt.val(); if(aceEditor !== undefined) { newDocumentContent = aceEditor.getValue(); } if(documentContent !== undefined && documentContent != newDocumentContent) { fileDesc.content = newDocumentContent; eventMgr.onContentChanged(fileDesc); } documentContent = newDocumentContent; } var previewWrapper; if(window.lightMode) { editor = new Markdown.EditorLight(converter); } else { editor = new Markdown.Editor(converter, undefined, { keyStrokes: shortcutMgr.getPagedownKeyStrokes() }); } // 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) { return true; } utils.resetModalInputs(); $(".modal-insert-image").modal(); return true; }); if(settings.lazyRendering === true) { previewWrapper = function(makePreview) { var debouncedMakePreview = _.debounce(makePreview, 500); return function() { if(documentContent === undefined) { makePreview(); eventMgr.onFileOpen(fileDesc); $previewContainerElt.scrollTop(fileDesc.previewScrollTop); if(window.lightMode) { $editorElt.scrollTop(fileDesc.editorScrollTop); } else { _.defer(function() { aceEditor.renderer.scrollToY(fileDesc.editorScrollTop); }); } } else { debouncedMakePreview(); } checkDocumentChanges(); }; }; } else { previewWrapper = function(makePreview) { return function() { makePreview(); if(documentContent === undefined) { eventMgr.onFileOpen(fileDesc); $previewContainerElt.scrollTop(fileDesc.previewScrollTop); if(window.lightMode) { $editorElt.scrollTop(fileDesc.editorScrollTop); } else { _.defer(function() { aceEditor.renderer.scrollToY(fileDesc.editorScrollTop); }); } } checkDocumentChanges(); }; }; } eventMgr.onPagedownConfigure(editor); editor.hooks.chain("onPreviewRefresh", eventMgr.onAsyncPreview); if(window.lightMode) { editor.run(previewWrapper); editor.undoManager.reinit(initDocumentContent, fileDesc.editorStart, fileDesc.editorEnd, fileDesc.editorScrollTop); $editorElt.focus(); } else { editor.run(aceEditor, previewWrapper); aceEditor.selection.setSelectionRange(fileDesc.editorSelectRange); aceEditor.focus(); } // Hide default buttons $(".wmd-button-row li").addClass("btn btn-success").css("left", 0).find("span").hide(); // Add customized buttons var $btnGroupElt = $('.wmd-button-group1'); $("#wmd-bold-button").append($('')).appendTo($btnGroupElt); $("#wmd-italic-button").append($('')).appendTo($btnGroupElt); $btnGroupElt = $('.wmd-button-group2'); $("#wmd-link-button").append($('')).appendTo($btnGroupElt); $("#wmd-quote-button").append($('')).appendTo($btnGroupElt); $("#wmd-code-button").append($('')).appendTo($btnGroupElt); $("#wmd-image-button").append($('')).appendTo($btnGroupElt); $btnGroupElt = $('.wmd-button-group3'); $("#wmd-olist-button").append($('')).appendTo($btnGroupElt); $("#wmd-ulist-button").append($('')).appendTo($btnGroupElt); $("#wmd-heading-button").append($('')).appendTo($btnGroupElt); $("#wmd-hr-button").append($('')).appendTo($btnGroupElt); $btnGroupElt = $('.wmd-button-group4'); $("#wmd-undo-button").append($('')).appendTo($btnGroupElt); $("#wmd-redo-button").append($('')).appendTo($btnGroupElt); }; // Shows a dialog to force the user to click a button before opening oauth popup var redirectCallbackConfirm; var redirectCallbackCancel; core.redirectConfirm = function(message, callbackConfirm, callbackCancel) { redirectCallbackConfirm = callbackConfirm; redirectCallbackCancel = callbackCancel; $('.modal-redirect-confirm .redirect-msg').html(message); $('.modal-redirect-confirm').modal("show"); }; // Initialize multiple things and then fire eventMgr.onReady var isDocumentPanelShown = false; var isMenuPanelShown = false; core.onReady = function() { if(window.viewerMode === true) { document.body.innerHTML = bodyViewerHTML; } else { document.body.innerHTML = bodyIndexHTML; } $navbarElt = $('.navbar'); $leftBtnElts = $navbarElt.find('.left-buttons'); $rightBtnElts = $navbarElt.find('.right-buttons'); $leftBtnDropdown = $navbarElt.find('.left-buttons-dropdown'); $rightBtnDropdown = $navbarElt.find('.right-buttons-dropdown'); $(window).bind("resize", adjustWindow); // Populate shortcuts in settings shortcutMgr.addSettingEntries(); // listen to online/offline events $(window).on('offline', core.setOffline); $(window).on('online', setOnline); if(navigator.onLine === false) { core.setOffline(); } // Detect user activity $(document).mousemove(setUserActive).keypress(setUserActive); // Avoid dropdown to close when clicking on submenu $(".dropdown-submenu > a").click(function(e) { e.stopPropagation(); }); $menuPanelElt = $('.menu-panel').collapse({ toggle: false }); var menuPanelBackdropElt; $menuPanelElt.on('show.bs.collapse', function(e) { if(e.target === $menuPanelElt[0]) { isMenuPanelShown = true; menuPanelBackdropElt = utils.createBackdrop('collapse', '.menu-panel'); $menuPanelElt.addClass('move-to-front'); // To avoid opening delay setTimeout(function() { $menuPanelElt.trigger($.support.transition.end); }, 50); } else { // Close all open sub-menus when one submenu opens $menuPanelElt.find('.in').collapse('hide'); } }).on('hide.bs.collapse', function(e) { if(e.target === $menuPanelElt[0]) { isMenuPanelShown = false; menuPanelBackdropElt.parentNode.removeChild(menuPanelBackdropElt); $menuPanelElt.removeClass('move-to-front'); aceEditor ? aceEditor.focus() : $editorElt.focus(); } }).on('hidden.bs.collapse', function(e) { if(e.target === $menuPanelElt[0]) { // Close all open sub-menus when menu panel is closed $menuPanelElt.find('.in').collapse('hide'); } }); $documentPanelElt = $('.document-panel').collapse({ toggle: false }); var documentPanelBackdropElt; $documentPanelElt.on('show.bs.collapse', function(e) { if(e.target === $documentPanelElt[0]) { isDocumentPanelShown = true; documentPanelBackdropElt = utils.createBackdrop('collapse', '.document-panel'); $documentPanelElt.addClass('move-to-front'); // To avoid opening delay setTimeout(function() { $documentPanelElt.trigger($.support.transition.end); }, 50); } else { // Close all open sub-menus when one submenu opens $documentPanelElt.find('.in').collapse('hide'); } }).on('hide.bs.collapse', function(e) { if(e.target === $documentPanelElt[0]) { isDocumentPanelShown = false; documentPanelBackdropElt.parentNode.removeChild(documentPanelBackdropElt); $documentPanelElt.removeClass('move-to-front'); aceEditor ? aceEditor.focus() : $editorElt.focus(); } }).on('hidden.bs.collapse', function(e) { if(e.target === $documentPanelElt[0]) { // Close all open sub-menus when menu panel is closed $documentPanelElt.find('.in').collapse('hide'); } }); // Editor if(window.lightMode) { // In light mode, we replace ACE with a textarea $('#wmd-input').replaceWith(function() { return $('