diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c3cb2b6..517cd25 100755 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -22,22 +22,28 @@ Stick to the conventions defined in other components as closely as possible. In order to maintain a consistant code structure to the code across the application please follow the wordpress standard, or run any changes through [JSBeautifier] (http://jsbeautifier.org/) with the settings below. { - "indent_size": "1", + "brace_style": "collapse", + "break_chained_methods": false, + "comma_first": false, + "e4x": false, + "end_with_newline": true, "indent_char": "\t", + "indent_empty_lines": true, + "indent_inner_html": true, + "indent_scripts": "normal", + "indent_size": "1", + "jslint_happy": false, + "keep_array_indentation": true, "max_preserve_newlines": "5", "preserve_newlines": true, - "keep_array_indentation": true, - "break_chained_methods": false, - "indent_scripts": "normal", - "brace_style": "collapse", + "space_after_anon_function": false, + "space_after_named_function": false, "space_before_conditional": false, + "space_in_empty_paren": false, + "space_in_paren": true, "unescape_strings": false, - "jslint_happy": false, - "end_with_newline": true, - "wrap_line_length": "0", - "indent_inner_html": true, - "comma_first": false, - "e4x": false + "unindent_chained_methods": true, + "wrap_line_length": "0" } If you have questions, please ask. Submit an issue or [contact us directly](mailto:support@telaaedifex.com). diff --git a/README.md b/README.md index 5a318f5..8bbd19e 100755 --- a/README.md +++ b/README.md @@ -9,11 +9,13 @@ Distributed under the MIT-Style License. See LICENSE.txt file for more informati Repositories: [GitLab](https://gitlab.com/xevidos/codiad) + [GitHub](https://github.com/xevidos/codiad) Issues: [GitLab](https://gitlab.com/xevidos/codiad/issues) + [GitHub](https://github.com/xevidos/codiad/issues) Features: @@ -37,16 +39,19 @@ Task List: * Add ability to login with LDAP * Add custom market -* Add in new admin interface ( Check admin-portal branch for progress ) +* \- Add in new admin interface ( Check admin-portal branch for progress ) - Group Management - Permissions Management - Plugin Management - Project Management - System Settings - User Management +* Add Drag and Drop natively to filemanager +* Add folder / filestructure upload ability * Add if file could not be saved 5 times close the open file * Add multi level users. ( Projects for only certain groups, Permission levels ) * Add mobile compatibility +* Add permissions module ( more in depth permissions such as read/write, delete, etc ) * Add support for more database systems ( MSSQL, Oracle, SQLite, Filesystem storage, etc ) * Add in auto save timer that saves after the user stops typing instead of after every change * Clean up update script @@ -70,7 +75,9 @@ Completed: * Updated for PHP 7.2 -Bugs: +Known Bugs: -* Auto save does not save the most recent changes every once in a while requiring more information to be typed ( E.G. A couple spaces ) in order to show up in saved file. -* Cursor is set to the wrong position if in split view. \ No newline at end of file +* Auto save does not save the most recent changes every once in a while requiring more information to be typed ( E.G. A couple spaces ) in order to show up in saved file +* Cursor is set to the wrong position if in split view +* In certain enviroments the update script pulls the old version of the sql class causing the update to fail +* The Server has new version of file alert causes auto save to stop when the user presses okay \ No newline at end of file diff --git a/components/active/init.js b/components/active/init.js index 369ad15..1c5a035 100755 --- a/components/active/init.js +++ b/components/active/init.js @@ -4,1014 +4,1022 @@ * warranty under the MIT License. See [root]/license.txt for more. * This information must remain intact. */ - -(function(global, $) { - - var EditSession = ace.require('ace/edit_session') - .EditSession; - var UndoManager = ace.require('ace/undomanager') - .UndoManager; - - var codiad = global.codiad; - - $(function() { - codiad.active.init(); - }); - - ////////////////////////////////////////////////////////////////// - // - // Active Files Component for Codiad - // --------------------------------- - // Track and manage EditSession instaces of files being edited. - // - ////////////////////////////////////////////////////////////////// - - codiad.active = { - - controller: 'components/active/controller.php', - - // Path to EditSession instance mapping - sessions: {}, - - // History of opened files - history: [], +( function( global, $ ) { + + var EditSession = ace.require( 'ace/edit_session' ) + .EditSession; + var UndoManager = ace.require( 'ace/undomanager' ) + .UndoManager; + + var codiad = global.codiad; + + $( function() { + codiad.active.init(); + }); + + ////////////////////////////////////////////////////////////////// + // + // Active Files Component for Codiad + // --------------------------------- + // Track and manage EditSession instaces of files being edited. + // + ////////////////////////////////////////////////////////////////// + + codiad.active = { + + controller: 'components/active/controller.php', + + // Path to EditSession instance mapping + sessions: {}, + + // History of opened files + history: [], // List of active file positions positions: {}, - - ////////////////////////////////////////////////////////////////// - // - // Check if a file is open. - // - // Parameters: - // path - {String} - // - ////////////////////////////////////////////////////////////////// - - isOpen: function(path) { - return !!this.sessions[path]; - }, - - open: function(path, content, mtime, inBackground, focus) { - - //if( this. ) { - - - //} - /* Notify listeners. */ - amplify.publish('active.onFileWillOpen', {path: path, content: content}); - - if (focus === undefined) { - focus = true; - } - - var _this = this; - - if (this.isOpen(path)) { - if(focus) this.focus(path); - return; - } - var ext = codiad.filemanager.getExtension(path); - var mode = codiad.editor.selectMode( path ); + + ////////////////////////////////////////////////////////////////// + // + // Check if a file is open. + // + // Parameters: + // path - {String} + // + ////////////////////////////////////////////////////////////////// + + isOpen: function( path ) { + return !!this.sessions[path]; + }, + + open: function( path, content, mtime, inBackground, focus ) { - var fn = function() { - - //var Mode = require('ace/mode/' + mode) - // .Mode; - - // TODO: Ask for user confirmation before recovering - // And maybe show a diff - var draft = _this.checkDraft(path); - if (draft) { - content = draft; - codiad.message.success(i18n('Recovered unsaved content for: ') + path); - } - - //var session = new EditSession(content, new Mode()); - var session = new EditSession(content); - session.setMode(mode.mode); - session.setUndoManager(new UndoManager()); - - session.path = path; - session.serverMTime = mtime; - _this.sessions[path] = session; - session.untainted = content.slice(0); - if (!inBackground && focus) { - codiad.editor.setSession(session); - } - _this.add(path, session, focus); - - if( ! ( _this.positions[`${path}`] === undefined ) && focus ) { - - _this.setPosition( _this.positions[`${path}`] ); - } - - /* Notify listeners. */ - amplify.publish('active.onOpen', path); - }; - - // Assuming the mode file has no dependencies - $.loadScript('components/editor/ace-editor/mode-' + mode.name + '.js', - fn); - }, - - init: function() { - - var _this = this; - - _this.initTabDropdownMenu(); - _this.updateTabDropdownVisibility(); - - // Focus from list. - $('#list-active-files a') - .live('click', function(e) { - e.stopPropagation(); - _this.focus($(this).parent('li').attr('data-path')); - }); - - // Focus on left button click from dropdown. - $('#dropdown-list-active-files a') - .live('click', function(e) { - if(e.which == 1) { - /* Do not stop propagation of the event, - * it will be catch by the dropdown menu - * and close it. */ - _this.focus($(this).parent('li').attr('data-path')); - } - }); - - // Focus on left button mousedown from tab. - $('#tab-list-active-files li.tab-item>a.label') - .live('mousedown', function(e) { - if(e.which == 1) { - e.stopPropagation(); - _this.focus($(this).parent('li').attr('data-path')); - } - }); + //if( this. ) { - // Remove from list. - $('#list-active-files a>span') - .live('click', function(e) { - e.stopPropagation(); - _this.remove($(this) - .parent('a') - .parent('li') - .attr('data-path')); - }); - - // Remove from dropdown. - $('#dropdown-list-active-files a>span') - .live('click', function(e) { - e.stopPropagation(); - /* Get the active editor before removing anything. Remove the - * tab, then put back the focus on the previously active - * editor if it was not removed. */ - var activePath = _this.getPath(); - var pathToRemove = $(this).parents('li').attr('data-path'); - _this.remove(pathToRemove); - if (activePath !== null && activePath !== pathToRemove) { - _this.focus(activePath); - } - _this.updateTabDropdownVisibility(); - }); - - // Remove from tab. - $('#tab-list-active-files a.close') - .live('click', function(e) { - e.stopPropagation(); - /* Get the active editor before removing anything. Remove the - * tab, then put back the focus on the previously active - * editor if it was not removed. */ - var activePath = _this.getPath(); - var pathToRemove = $(this).parent('li').attr('data-path'); - _this.remove(pathToRemove); - if (activePath !== null && activePath !== pathToRemove) { - _this.focus(activePath); - } - _this.updateTabDropdownVisibility(); - }); - - // Remove from middle button click on dropdown. - $('#dropdown-list-active-files li') - .live('mouseup', function(e) { - if (e.which == 2) { - e.stopPropagation(); - /* Get the active editor before removing anything. Remove the - * tab, then put back the focus on the previously active - * editor if it was not removed. */ - var activePath = _this.getPath(); - var pathToRemove = $(this).attr('data-path'); - _this.remove(pathToRemove); - if (activePath !== null && activePath !== pathToRemove) { - _this.focus(activePath); - } - _this.updateTabDropdownVisibility(); - } - }); - - // Remove from middle button click on tab. - $('.tab-item') - .live('mouseup', function(e) { - if (e.which == 2) { - e.stopPropagation(); - /* Get the active editor before removing anything. Remove the - * tab, then put back the focus on the previously active - * editor if it was not removed. */ - var activePath = _this.getPath(); - var pathToRemove = $(this).attr('data-path'); - _this.remove(pathToRemove); - if (activePath !== null && activePath !== pathToRemove) { - _this.focus(activePath); - } - _this.updateTabDropdownVisibility(); - } - }); - - // Make list sortable - $('#list-active-files') - .sortable({ - placeholder: 'active-sort-placeholder', - tolerance: 'intersect', - start: function(e, ui) { - ui.placeholder.height(ui.item.height()); - } - }); - - // Make dropdown sortable. - $('#dropdown-list-active-files') - .sortable({ - axis: 'y', - tolerance: 'pointer', - start: function(e, ui) { - ui.placeholder.height(ui.item.height()); - } - }); - - // Make tabs sortable. - $('#tab-list-active-files') - .sortable({ - items: '> li', - axis: 'x', - tolerance: 'pointer', - containment: 'parent', - start: function(e, ui) { - ui.placeholder.css('background', 'transparent'); - ui.helper.css('width', '200px'); - }, - stop: function(e, ui) { - // Reset css - ui.item.css('z-index', '') - ui.item.css('position', '') - } - }); - /* Woaw, so tricky! At initialization, the tab-list is empty, so - * it is not marked as float so it is not detected as an horizontal - * list by the sortable plugin. Workaround is to mark it as - * floating at initialization time. See bug report - * http://bugs.jqueryui.com/ticket/6702. */ - $('#tab-list-active-files').data('sortable').floating = true; - - // Open saved-state active files on load - $.get(_this.controller + '?action=list', function(data) { - var listResponse = codiad.jsend.parse(data); - if (listResponse !== null) { - $.each(listResponse, function(index, data) { - - codiad.active.positions[`${data.path}`] = JSON.parse( data.position ); - codiad.filemanager.openFile(data.path, data.focused); - }); - } - }); - // Prompt if a user tries to close window without saving all filess - window.onbeforeunload = function(e) { - - codiad.active.uploadPositions(); - if ($('#list-active-files li.changed') - .length > 0) { - var e = e || window.event; - var errMsg = i18n('You have unsaved files.'); - - // For IE and Firefox prior to version 4 - if (e) { - e.returnValue = errMsg; - } - - // For rest - return errMsg; - } - }; - }, - - ////////////////////////////////////////////////////////////////// - // Drafts - ////////////////////////////////////////////////////////////////// - - checkDraft: function(path) { - var draft = localStorage.getItem(path); - if (draft !== null) { - return draft; - } else { - return false; - } - }, - - removeDraft: function(path) { - localStorage.removeItem(path); - }, - - ////////////////////////////////////////////////////////////////// - // Get active editor path - ////////////////////////////////////////////////////////////////// - - getPath: function() { - try { - return codiad.editor.getActive() - .getSession() - .path; - } catch (e) { - return null; - } - }, - - ////////////////////////////////////////////////////////////////// - // Check if opened by another user - ////////////////////////////////////////////////////////////////// - - check: function(path) { - $.get(this.controller + '?action=check&path=' + encodeURIComponent(path), - - function(data) { - var checkResponse = codiad.jsend.parse(data); - }); - }, - - ////////////////////////////////////////////////////////////////// - // Add newly opened file to list - ////////////////////////////////////////////////////////////////// - - add: function(path, session, focus) { - if (focus === undefined) { - focus = true; - } - - var listThumb = this.createListThumb(path); - session.listThumb = listThumb; - $('#list-active-files').append(listThumb); - - /* If the tab list would overflow with the new tab. Move the - * first tab to dropdown, then add a new tab. */ - if (this.isTabListOverflowed(true)) { - var tab = $('#tab-list-active-files li:first-child'); - this.moveTabToDropdownMenu(tab); - } - - var tabThumb = this.createTabThumb(path); - $('#tab-list-active-files').append(tabThumb); - session.tabThumb = tabThumb; - - this.updateTabDropdownVisibility(); - - $.get(this.controller + '?action=add&path=' + encodeURIComponent(path)); - - if(focus) { - this.focus(path); - } - - // Mark draft as changed - if (this.checkDraft(path)) { - this.markChanged(path); - } - }, - - ////////////////////////////////////////////////////////////////// - // Focus on opened file - ////////////////////////////////////////////////////////////////// - - focus: function(path, moveToTabList) { - if (moveToTabList === undefined) { - moveToTabList = true; - } - - /* Notify listeners. */ - amplify.publish('active.onWillFocus', path); - - this.highlightEntry(path, moveToTabList); - - if(path != this.getPath()) { - - let _this = this; - codiad.editor.setSession(this.sessions[path]); - this.history.push(path); - $.get(this.controller, {'action':'focused', 'path':path}, function() { - - if( ! ( _this.positions[`${path}`] === undefined ) ) { - - _this.setPosition( _this.positions[`${path}`] ); - } - }); - } - - /* Check for users registered on the file. */ - this.check(path); + //} + /* Notify listeners. */ + amplify.publish( 'active.onFileWillOpen', { + path: path, + content: content + }); - /* Notify listeners. */ - amplify.publish('active.onFocus', path); - }, - - highlightEntry: function(path, moveToTabList) { - if (moveToTabList === undefined) { - moveToTabList = true; - } - - $('#list-active-files li') - .removeClass('active'); - - $('#tab-list-active-files li') - .removeClass('active'); - - $('#dropdown-list-active-files li') - .removeClass('active'); - - var session = this.sessions[path]; - - if($('#dropdown-list-active-files').has(session.tabThumb).length > 0) { - if(moveToTabList) { - /* Get the menu item as a tab, and put the last tab in - * dropdown. */ - var menuItem = session.tabThumb; - this.moveDropdownMenuItemToTab(menuItem, true); - - var tab = $('#tab-list-active-files li:last-child'); - this.moveTabToDropdownMenu(tab); - } else { - /* Show the dropdown menu if needed */ - this.showTabDropdownMenu(); - } - } - else if(this.history.length > 0) { - var prevPath = this.history[this.history.length-1]; - var prevSession = this.sessions[prevPath]; - if($('#dropdown-list-active-files').has(prevSession.tabThumb).length > 0) { - /* Hide the dropdown menu if needed */ - this.hideTabDropdownMenu(); - } - } - - session.tabThumb.addClass('active'); - session.listThumb.addClass('active'); - }, - - ////////////////////////////////////////////////////////////////// - // Mark changed - ////////////////////////////////////////////////////////////////// - - markChanged: function(path) { - this.sessions[path].listThumb.addClass('changed'); - this.sessions[path].tabThumb.addClass('changed'); - }, - - ////////////////////////////////////////////////////////////////// - // Save active editor - ////////////////////////////////////////////////////////////////// - - save: function(path, alerts=true) { - /* Notify listeners. */ - amplify.publish('active.onSave', path); - - var _this = this; - if ((path && !this.isOpen(path)) || (!path && !codiad.editor.getActive())) { - codiad.message.error(i18n('No Open Files to save')); - return; - } - var session; - if (path) session = this.sessions[path]; - else session = codiad.editor.getActive() - .getSession(); - var content = session.getValue(); - var path = session.path; - var handleSuccess = function(mtime){ - var session = codiad.active.sessions[path]; - if(typeof session != 'undefined') { - session.untainted = newContent; - session.serverMTime = mtime; - if (session.listThumb) session.listThumb.removeClass('changed'); - if (session.tabThumb) session.tabThumb.removeClass('changed'); - } - _this.removeDraft(path); - } - // Replicate the current content so as to avoid - // discrepancies due to content changes during - // computation of diff - - var newContent = content.slice(0); - if (session.serverMTime && session.untainted){ - codiad.workerManager.addTask({ - taskType: 'diff', - id: path, - original: session.untainted, - changed: newContent - }, function(success, patch){ - if (success) { - codiad.filemanager.savePatch(path, patch, session.serverMTime, { - success: handleSuccess - }, alerts); - } else { - codiad.filemanager.saveFile(path, newContent, { - success: handleSuccess - }, alerts); - } - }, this); - } else { - codiad.filemanager.saveFile(path, newContent, { - success: handleSuccess - }, alert); - } - }, - - ////////////////////////////////////////////////////////////////// - // Save all files - ////////////////////////////////////////////////////////////////// - - saveAll: function() { - var _this = this; - for(var session in _this.sessions) { - if (_this.sessions[session].listThumb.hasClass('changed')) { - codiad.active.save(session); - } - } - }, - - ////////////////////////////////////////////////////////////////// - // Remove file - ////////////////////////////////////////////////////////////////// - - remove: function(path) { - if (!this.isOpen(path)) return; - var session = this.sessions[path]; - var closeFile = true; - if (session.listThumb.hasClass('changed')) { - codiad.modal.load(450, 'components/active/dialog.php?action=confirm&path=' + encodeURIComponent(path)); - closeFile = false; - } - if (closeFile) { - this.close(path); - } - }, - - removeAll: function(discard) { - discard = discard || false; - /* Notify listeners. */ - amplify.publish('active.onRemoveAll'); - - var _this = this; - var changed = false; - - var opentabs = new Array(); - for(var session in _this.sessions) { - opentabs[session] = session; - if (_this.sessions[session].listThumb.hasClass('changed')) { - changed = true; - } - } - if(changed && !discard) { - codiad.modal.load(450, 'components/active/dialog.php?action=confirmAll'); - return; - } - - for(var tab in opentabs) { - var session = this.sessions[tab]; - - session.tabThumb.remove(); - _this.updateTabDropdownVisibility(); - - session.listThumb.remove(); - - /* Remove closed path from history */ - var history = []; - $.each(this.history, function(index) { - if(this != tab) history.push(this); - }) - this.history = history - - delete this.sessions[tab]; - this.removeDraft(tab); - } - codiad.editor.exterminate(); - $('#list-active-files').html(''); - $.get(this.controller + '?action=removeall'); - }, - - close: function(path) { - /* Notify listeners. */ - amplify.publish('active.onClose', path); - - var _this = this; - var session = this.sessions[path]; - - /* Animate only if the tabThumb if a tab, not a dropdown item. */ - if(session.tabThumb.hasClass('tab-item')) { - session.tabThumb.css({'z-index': 1}); - session.tabThumb.animate({ - top: $('#editor-top-bar').height() + 'px' - }, 300, function() { - session.tabThumb.remove(); - _this.updateTabDropdownVisibility(); - }); - } else { - session.tabThumb.remove(); - _this.updateTabDropdownVisibility(); - } - - session.listThumb.remove(); - - /* Remove closed path from history */ - var history = []; - $.each(this.history, function(index) { - if(this != path) history.push(this); - }) - this.history = history - - /* Select all the tab tumbs except the one which is to be removed. */ - var tabThumbs = $('#tab-list-active-files li[data-path!="' + path + '"]'); - - if (tabThumbs.length == 0) { - codiad.editor.exterminate(); - } else { - - var nextPath = ''; - if(this.history.length > 0) { - nextPath = this.history[this.history.length - 1]; - } else { - nextPath = $(tabThumbs[0]).attr('data-path'); - } - var nextSession = this.sessions[nextPath]; - codiad.editor.removeSession(session, nextSession); - - this.focus(nextPath); - } - delete this.sessions[path]; - $.get(this.controller + '?action=remove&path=' + encodeURIComponent(path)); - this.removeDraft(path); - }, - - ////////////////////////////////////////////////////////////////// - // Process rename - ////////////////////////////////////////////////////////////////// - - rename: function(oldPath, newPath) { - var switchSessions = function(oldPath, newPath) { - var tabThumb = this.sessions[oldPath].tabThumb; - tabThumb.attr('data-path', newPath); - var title = newPath; - if (codiad.project.isAbsPath(newPath)) { - title = newPath.substring(1); - } - tabThumb.find('.label') - .text(title); - this.sessions[newPath] = this.sessions[oldPath]; - this.sessions[newPath].path = newPath; - delete this.sessions[oldPath]; - //Rename history - for (var i = 0; i < this.history.length; i++) { - if (this.history[i] === oldPath) { - this.history[i] = newPath; - } - } - }; - if (this.sessions[oldPath]) { - // A file was renamed - switchSessions.apply(this, [oldPath, newPath]); - // pass new sessions instance to setactive - for (var k = 0; k < codiad.editor.instances.length; k++) { - if (codiad.editor.instances[k].getSession().path === newPath) { - codiad.editor.setActive(codiad.editor.instances[k]); - } - } - - var newSession = this.sessions[newPath]; - - // Change Editor Mode - var mode = codiad.editor.selectMode(newPath); + if( focus === undefined ) { + focus = true; + } + + var _this = this; + + if( this.isOpen( path ) ) { + if( focus ) this.focus( path ); + return; + } + var ext = codiad.filemanager.getExtension( path ); + var mode = codiad.editor.selectMode( path ); + + var fn = function() { - // handle async mode change - var fn = function() { - codiad.editor.setModeDisplay(newSession); - newSession.removeListener('changeMode', fn); - } - - newSession.on("changeMode", fn); - newSession.setMode( mode.mode ); - } else { - // A folder was renamed - var newKey; - for (var key in this.sessions) { - newKey = key.replace(oldPath, newPath); - if (newKey !== key) { - switchSessions.apply(this, [key, newKey]); - } - } - } - $.get(this.controller + '?action=rename&old_path=' + encodeURIComponent(oldPath) + '&new_path=' + encodeURIComponent(newPath), function() { - /* Notify listeners. */ - amplify.publish('active.onRename', {"oldPath": oldPath, "newPath": newPath}); - }); - }, - - ////////////////////////////////////////////////////////////////// - // Open in Browser - ////////////////////////////////////////////////////////////////// - - openInBrowser: function() { - var path = this.getPath(); - if (path) { - codiad.filemanager.openInBrowser(path); - } else { - codiad.message.error('No Open Files'); - } - }, - - ////////////////////////////////////////////////////////////////// - // Get Selected Text - ////////////////////////////////////////////////////////////////// - - getSelectedText: function() { - var path = this.getPath(); - var session = this.sessions[path]; - - if (path && this.isOpen(path)) { - return session.getTextRange( - codiad.editor.getActive() - .getSelectionRange()); - } else { - codiad.message.error(i18n('No Open Files or Selected Text')); - } - }, - - ////////////////////////////////////////////////////////////////// - // Insert Text - ////////////////////////////////////////////////////////////////// - - insertText: function(val) { - codiad.editor.getActive() - .insert(val); - }, - - ////////////////////////////////////////////////////////////////// - // Goto Line - ////////////////////////////////////////////////////////////////// - - gotoLine: function(line) { - codiad.editor.getActive() - .gotoLine(line, 0, true); - }, - - ////////////////////////////////////////////////////////////////// - // Move Up (Key Combo) - ////////////////////////////////////////////////////////////////// - - move: function(dir) { - - var num = $('#tab-list-active-files li').length; - if (num === 0) return; - - var newActive = null; - var active = null; - - if (dir == 'up') { - - // If active is in the tab list - active = $('#tab-list-active-files li.active'); - if(active.length > 0) { - // Previous or rotate to the end - newActive = active.prev('li'); - if (newActive.length === 0) { - newActive = $('#dropdown-list-active-files li:last-child') - if (newActive.length === 0) { - newActive = $('#tab-list-active-files li:last-child') - } - } - } - - // If active is in the dropdown list - active = $('#dropdown-list-active-files li.active'); - if(active.length > 0) { - // Previous - newActive = active.prev('li'); - if (newActive.length === 0) { - newActive = $('#tab-list-active-files li:last-child') - } - } - - } else { - - // If active is in the tab list - active = $('#tab-list-active-files li.active'); - if(active.length > 0) { - // Next or rotate to the beginning - newActive = active.next('li'); - if (newActive.length === 0) { - newActive = $('#dropdown-list-active-files li:first-child'); - if (newActive.length === 0) { - newActive = $('#tab-list-active-files li:first-child') - } - } - } - - // If active is in the dropdown list - active = $('#dropdown-list-active-files li.active'); - if(active.length > 0) { - // Next or rotate to the beginning - newActive = active.next('li'); - if (newActive.length === 0) { - newActive = $('#tab-list-active-files li:first-child') - } - } - - } - - if(newActive) this.focus(newActive.attr('data-path'), false); - }, - - ////////////////////////////////////////////////////////////////// - // Dropdown Menu - ////////////////////////////////////////////////////////////////// - - initTabDropdownMenu: function() { - var _this = this; - - var menu = $('#dropdown-list-active-files'); - var button = $('#tab-dropdown-button'); - var closebutton = $('#tab-close-button'); - - menu.appendTo($('body')); - - button.click(function(e) { - e.stopPropagation(); - _this.toggleTabDropdownMenu(); - }); - - closebutton.click(function(e) { - e.stopPropagation(); - _this.removeAll(); - }); - }, - - showTabDropdownMenu: function() { - var menu = $('#dropdown-list-active-files'); - if(!menu.is(':visible')) this.toggleTabDropdownMenu(); - }, - - hideTabDropdownMenu: function() { - var menu = $('#dropdown-list-active-files'); - if(menu.is(':visible')) this.toggleTabDropdownMenu(); - }, - - toggleTabDropdownMenu: function() { - var _this = this; - var menu = $('#dropdown-list-active-files'); - - menu.css({ - top: $("#editor-top-bar").height() + 'px', - right: '20px', - width: '200px' - }); - - menu.slideToggle('fast'); - - if(menu.is(':visible')) { - // handle click-out autoclosing - var fn = function() { - menu.hide(); - $(window).off('click', fn) - } - $(window).on('click', fn); - } - }, - - moveTabToDropdownMenu: function(tab, prepend) { - if (prepend === undefined) { - prepend = false; - } - - tab.remove(); - path = tab.attr('data-path'); - - var tabThumb = this.createMenuItemThumb(path); - if(prepend) $('#dropdown-list-active-files').prepend(tabThumb); - else $('#dropdown-list-active-files').append(tabThumb); - - if(tab.hasClass("changed")) { - tabThumb.addClass("changed"); - } - - if(tab.hasClass("active")) { - tabThumb.addClass("active"); - } - - this.sessions[path].tabThumb = tabThumb; - }, - - moveDropdownMenuItemToTab: function(menuItem, prepend) { - if (prepend === undefined) { - prepend = false; - } - - menuItem.remove(); - path = menuItem.attr('data-path'); - - var tabThumb = this.createTabThumb(path); - if(prepend) $('#tab-list-active-files').prepend(tabThumb); - else $('#tab-list-active-files').append(tabThumb); - - if(menuItem.hasClass("changed")) { - tabThumb.addClass("changed"); - } - - if(menuItem.hasClass("active")) { - tabThumb.addClass("active"); - } - - this.sessions[path].tabThumb = tabThumb; - }, - - isTabListOverflowed: function(includeFictiveTab) { - if (includeFictiveTab === undefined) { - includeFictiveTab = false; - } - - var tabs = $('#tab-list-active-files li'); - var count = tabs.length - if (includeFictiveTab) count += 1; - if (count <= 1) return false; - - var width = 0; - tabs.each(function(index) { - width += $(this).outerWidth(true); - }) - if (includeFictiveTab) { - width += $(tabs[tabs.length-1]).outerWidth(true); - } - - /* If we subtract the width of the left side bar, of the right side - * bar handle and of the tab dropdown handle to the window width, - * do we have enough room for the tab list? Its kind of complicated - * to handle all the offsets, so afterwards we add a fixed offset - * just t be sure. */ - var lsbarWidth = $(".sidebar-handle").width(); - if (codiad.sidebars.isLeftSidebarOpen) { - lsbarWidth = $("#sb-left").width(); - } - - var rsbarWidth = $(".sidebar-handle").width(); - if (codiad.sidebars.isRightSidebarOpen) { - rsbarWidth = $("#sb-right").width(); - } - - var tabListWidth = $("#tab-list-active-files").width(); - var dropdownWidth = $('#tab-dropdown').width(); - var closeWidth = $('#tab-close').width(); - var room = window.innerWidth - lsbarWidth - rsbarWidth - dropdownWidth - closeWidth - width - 30; - return (room < 0); - }, - - updateTabDropdownVisibility: function() { - while(this.isTabListOverflowed()) { - var tab = $('#tab-list-active-files li:last-child'); - if (tab.length == 1) this.moveTabToDropdownMenu(tab, true); - else break; - } - - while(!this.isTabListOverflowed(true)) { - var menuItem = $('#dropdown-list-active-files li:first-child'); - if (menuItem.length == 1) this.moveDropdownMenuItemToTab(menuItem); - else break; - } - - if ($('#dropdown-list-active-files li').length > 0) { - $('#tab-dropdown').show(); - } else { - $('#tab-dropdown').hide(); - // Be sure to hide the menu if it is opened. - $('#dropdown-list-active-files').hide(); - } - if ($('#tab-list-active-files li').length > 1) { - $('#tab-close').show(); - } else { - $('#tab-close').hide(); - } - }, + //var Mode = require('ace/mode/' + mode) + // .Mode; + + // TODO: Ask for user confirmation before recovering + // And maybe show a diff + var draft = _this.checkDraft( path ); + if( draft ) { + content = draft; + codiad.message.success( i18n( 'Recovered unsaved content for: ' ) + path ); + } + + //var session = new EditSession(content, new Mode()); + var session = new EditSession( content ); + session.setMode( mode.mode ); + session.setUndoManager( new UndoManager() ); + + session.path = path; + session.serverMTime = mtime; + _this.sessions[path] = session; + session.untainted = content.slice( 0 ); + if( !inBackground && focus ) { + codiad.editor.setSession( session ); + } + _this.add( path, session, focus ); + + if( !( _this.positions[`${path}`] === undefined ) && focus ) { + + _this.setPosition( _this.positions[`${path}`] ); + } + + /* Notify listeners. */ + amplify.publish( 'active.onOpen', path ); + }; + + // Assuming the mode file has no dependencies + $.loadScript( 'components/editor/ace-editor/mode-' + mode.name + '.js', + fn ); + }, + + init: function() { + + var _this = this; + + _this.initTabDropdownMenu(); + _this.updateTabDropdownVisibility(); + + // Focus from list. + $( '#list-active-files a' ) + .live( 'click', function( e ) { + e.stopPropagation(); + _this.focus( $( this ).parent( 'li' ).attr( 'data-path' ) ); + }); + + // Focus on left button click from dropdown. + $( '#dropdown-list-active-files a' ) + .live( 'click', function( e ) { + if( e.which == 1 ) { + /* Do not stop propagation of the event, + * it will be catch by the dropdown menu + * and close it. */ + _this.focus( $( this ).parent( 'li' ).attr( 'data-path' ) ); + } + }); + + // Focus on left button mousedown from tab. + $( '#tab-list-active-files li.tab-item>a.label' ) + .live( 'mousedown', function( e ) { + if( e.which == 1 ) { + e.stopPropagation(); + _this.focus( $( this ).parent( 'li' ).attr( 'data-path' ) ); + } + }); + + // Remove from list. + $( '#list-active-files a>span' ) + .live( 'click', function( e ) { + e.stopPropagation(); + _this.remove( $( this ) + .parent( 'a' ) + .parent( 'li' ) + .attr( 'data-path' ) ); + }); + + // Remove from dropdown. + $( '#dropdown-list-active-files a>span' ) + .live( 'click', function( e ) { + e.stopPropagation(); + /* Get the active editor before removing anything. Remove the + * tab, then put back the focus on the previously active + * editor if it was not removed. */ + var activePath = _this.getPath(); + var pathToRemove = $( this ).parents( 'li' ).attr( 'data-path' ); + _this.remove( pathToRemove ); + if( activePath !== null && activePath !== pathToRemove ) { + _this.focus( activePath ); + } + _this.updateTabDropdownVisibility(); + }); + + // Remove from tab. + $( '#tab-list-active-files a.close' ) + .live( 'click', function( e ) { + e.stopPropagation(); + /* Get the active editor before removing anything. Remove the + * tab, then put back the focus on the previously active + * editor if it was not removed. */ + var activePath = _this.getPath(); + var pathToRemove = $( this ).parent( 'li' ).attr( 'data-path' ); + _this.remove( pathToRemove ); + if( activePath !== null && activePath !== pathToRemove ) { + _this.focus( activePath ); + } + _this.updateTabDropdownVisibility(); + }); + + // Remove from middle button click on dropdown. + $( '#dropdown-list-active-files li' ) + .live( 'mouseup', function( e ) { + if( e.which == 2 ) { + e.stopPropagation(); + /* Get the active editor before removing anything. Remove the + * tab, then put back the focus on the previously active + * editor if it was not removed. */ + var activePath = _this.getPath(); + var pathToRemove = $( this ).attr( 'data-path' ); + _this.remove( pathToRemove ); + if( activePath !== null && activePath !== pathToRemove ) { + _this.focus( activePath ); + } + _this.updateTabDropdownVisibility(); + } + }); + + // Remove from middle button click on tab. + $( '.tab-item' ) + .live( 'mouseup', function( e ) { + if( e.which == 2 ) { + e.stopPropagation(); + /* Get the active editor before removing anything. Remove the + * tab, then put back the focus on the previously active + * editor if it was not removed. */ + var activePath = _this.getPath(); + var pathToRemove = $( this ).attr( 'data-path' ); + _this.remove( pathToRemove ); + if( activePath !== null && activePath !== pathToRemove ) { + _this.focus( activePath ); + } + _this.updateTabDropdownVisibility(); + } + }); + + // Make list sortable + $( '#list-active-files' ) + .sortable( { + placeholder: 'active-sort-placeholder', + tolerance: 'intersect', + start: function( e, ui ) { + ui.placeholder.height( ui.item.height() ); + } + }); + + // Make dropdown sortable. + $( '#dropdown-list-active-files' ) + .sortable( { + axis: 'y', + tolerance: 'pointer', + start: function( e, ui ) { + ui.placeholder.height( ui.item.height() ); + } + }); + + // Make tabs sortable. + $( '#tab-list-active-files' ) + .sortable( { + items: '> li', + axis: 'x', + tolerance: 'pointer', + containment: 'parent', + start: function( e, ui ) { + ui.placeholder.css( 'background', 'transparent' ); + ui.helper.css( 'width', '200px' ); + }, + stop: function( e, ui ) { + // Reset css + ui.item.css( 'z-index', '' ) + ui.item.css( 'position', '' ) + } + }); + /* Woaw, so tricky! At initialization, the tab-list is empty, so + * it is not marked as float so it is not detected as an horizontal + * list by the sortable plugin. Workaround is to mark it as + * floating at initialization time. See bug report + * http://bugs.jqueryui.com/ticket/6702. */ + $( '#tab-list-active-files' ).data( 'sortable' ).floating = true; + + // Open saved-state active files on load + $.get( _this.controller + '?action=list', function( data ) { + var listResponse = codiad.jsend.parse( data ); + if( listResponse !== null ) { + $.each( listResponse, function( index, data ) { + + codiad.active.positions[`${data.path}`] = JSON.parse( data.position ); + codiad.filemanager.openFile( data.path, data.focused ); + }); + } + }); + + // Prompt if a user tries to close window without saving all filess + window.onbeforeunload = function( e ) { + + codiad.active.uploadPositions(); + if( $( '#list-active-files li.changed' ) + .length > 0 ) { + var e = e || window.event; + var errMsg = i18n( 'You have unsaved files.' ); + + // For IE and Firefox prior to version 4 + if( e ) { + e.returnValue = errMsg; + } + + // For rest + return errMsg; + } + }; + }, + + ////////////////////////////////////////////////////////////////// + // Drafts + ////////////////////////////////////////////////////////////////// + + checkDraft: function( path ) { + var draft = localStorage.getItem( path ); + if( draft !== null ) { + return draft; + } else { + return false; + } + }, + + removeDraft: function( path ) { + localStorage.removeItem( path ); + }, + + ////////////////////////////////////////////////////////////////// + // Get active editor path + ////////////////////////////////////////////////////////////////// + + getPath: function() { + try { + return codiad.editor.getActive() + .getSession() + .path; + } catch ( e ) { + return null; + } + }, + + ////////////////////////////////////////////////////////////////// + // Check if opened by another user + ////////////////////////////////////////////////////////////////// + + check: function( path ) { + $.get( this.controller + '?action=check&path=' + encodeURIComponent( path ), + + function( data ) { + var checkResponse = codiad.jsend.parse( data ); + }); + }, + + ////////////////////////////////////////////////////////////////// + // Add newly opened file to list + ////////////////////////////////////////////////////////////////// + + add: function( path, session, focus ) { + if( focus === undefined ) { + focus = true; + } + + var listThumb = this.createListThumb( path ); + session.listThumb = listThumb; + $( '#list-active-files' ).append( listThumb ); + + /* If the tab list would overflow with the new tab. Move the + * first tab to dropdown, then add a new tab. */ + if( this.isTabListOverflowed( true ) ) { + var tab = $( '#tab-list-active-files li:first-child' ); + this.moveTabToDropdownMenu( tab ); + } + + var tabThumb = this.createTabThumb( path ); + $( '#tab-list-active-files' ).append( tabThumb ); + session.tabThumb = tabThumb; + + this.updateTabDropdownVisibility(); + + $.get( this.controller + '?action=add&path=' + encodeURIComponent( path ) ); + + if( focus ) { + this.focus( path ); + } + + // Mark draft as changed + if( this.checkDraft( path ) ) { + this.markChanged( path ); + } + }, + + ////////////////////////////////////////////////////////////////// + // Focus on opened file + ////////////////////////////////////////////////////////////////// + + focus: function( path, moveToTabList ) { + if( moveToTabList === undefined ) { + moveToTabList = true; + } + + /* Notify listeners. */ + amplify.publish( 'active.onWillFocus', path ); + + this.highlightEntry( path, moveToTabList ); + + if( path != this.getPath() ) { + + let _this = this; + codiad.editor.setSession( this.sessions[path] ); + this.history.push( path ); + $.get( this.controller, { + 'action': 'focused', + 'path': path + }, function() { + + if( !( _this.positions[`${path}`] === undefined ) ) { + + _this.setPosition( _this.positions[`${path}`] ); + } + }); + } + + /* Check for users registered on the file. */ + this.check( path ); + + /* Notify listeners. */ + amplify.publish( 'active.onFocus', path ); + }, + + highlightEntry: function( path, moveToTabList ) { + if( moveToTabList === undefined ) { + moveToTabList = true; + } + + $( '#list-active-files li' ) + .removeClass( 'active' ); + + $( '#tab-list-active-files li' ) + .removeClass( 'active' ); + + $( '#dropdown-list-active-files li' ) + .removeClass( 'active' ); + + var session = this.sessions[path]; + + if( $( '#dropdown-list-active-files' ).has( session.tabThumb ).length > 0 ) { + if( moveToTabList ) { + /* Get the menu item as a tab, and put the last tab in + * dropdown. */ + var menuItem = session.tabThumb; + this.moveDropdownMenuItemToTab( menuItem, true ); + + var tab = $( '#tab-list-active-files li:last-child' ); + this.moveTabToDropdownMenu( tab ); + } else { + /* Show the dropdown menu if needed */ + this.showTabDropdownMenu(); + } + } else if( this.history.length > 0 ) { + var prevPath = this.history[this.history.length - 1]; + var prevSession = this.sessions[prevPath]; + if( $( '#dropdown-list-active-files' ).has( prevSession.tabThumb ).length > 0 ) { + /* Hide the dropdown menu if needed */ + this.hideTabDropdownMenu(); + } + } + + session.tabThumb.addClass( 'active' ); + session.listThumb.addClass( 'active' ); + }, + + ////////////////////////////////////////////////////////////////// + // Mark changed + ////////////////////////////////////////////////////////////////// + + markChanged: function( path ) { + this.sessions[path].listThumb.addClass( 'changed' ); + this.sessions[path].tabThumb.addClass( 'changed' ); + }, + + ////////////////////////////////////////////////////////////////// + // Save active editor + ////////////////////////////////////////////////////////////////// + + save: function( path, alerts = true ) { + /* Notify listeners. */ + amplify.publish( 'active.onSave', path ); + + var _this = this; + if( ( path && !this.isOpen( path ) ) || ( !path && !codiad.editor.getActive() ) ) { + codiad.message.error( i18n( 'No Open Files to save' ) ); + return; + } + var session; + if( path ) session = this.sessions[path]; + else session = codiad.editor.getActive() + .getSession(); + var content = session.getValue(); + var path = session.path; + var handleSuccess = function( mtime ) { + var session = codiad.active.sessions[path]; + if( typeof session != 'undefined' ) { + session.untainted = newContent; + session.serverMTime = mtime; + if( session.listThumb ) session.listThumb.removeClass( 'changed' ); + if( session.tabThumb ) session.tabThumb.removeClass( 'changed' ); + } + _this.removeDraft( path ); + } + // Replicate the current content so as to avoid + // discrepancies due to content changes during + // computation of diff + + var newContent = content.slice( 0 ); + if( session.serverMTime && session.untainted ) { + codiad.workerManager.addTask( { + taskType: 'diff', + id: path, + original: session.untainted, + changed: newContent + }, function( success, patch ) { + if( success ) { + codiad.filemanager.savePatch( path, patch, session.serverMTime, { + success: handleSuccess + }, alerts ); + } else { + codiad.filemanager.saveFile( path, newContent, { + success: handleSuccess + }, alerts ); + } + }, this ); + } else { + codiad.filemanager.saveFile( path, newContent, { + success: handleSuccess + }, alert ); + } + }, + + ////////////////////////////////////////////////////////////////// + // Save all files + ////////////////////////////////////////////////////////////////// + + saveAll: function() { + var _this = this; + for( var session in _this.sessions ) { + if( _this.sessions[session].listThumb.hasClass( 'changed' ) ) { + codiad.active.save( session ); + } + } + }, + + ////////////////////////////////////////////////////////////////// + // Remove file + ////////////////////////////////////////////////////////////////// + + remove: function( path ) { + if( !this.isOpen( path ) ) return; + var session = this.sessions[path]; + var closeFile = true; + if( session.listThumb.hasClass( 'changed' ) ) { + codiad.modal.load( 450, 'components/active/dialog.php?action=confirm&path=' + encodeURIComponent( path ) ); + closeFile = false; + } + if( closeFile ) { + this.close( path ); + } + }, + + removeAll: function( discard ) { + discard = discard || false; + /* Notify listeners. */ + amplify.publish( 'active.onRemoveAll' ); + + var _this = this; + var changed = false; + + var opentabs = new Array(); + for( var session in _this.sessions ) { + opentabs[session] = session; + if( _this.sessions[session].listThumb.hasClass( 'changed' ) ) { + changed = true; + } + } + if( changed && !discard ) { + codiad.modal.load( 450, 'components/active/dialog.php?action=confirmAll' ); + return; + } + + for( var tab in opentabs ) { + var session = this.sessions[tab]; + + session.tabThumb.remove(); + _this.updateTabDropdownVisibility(); + + session.listThumb.remove(); + + /* Remove closed path from history */ + var history = []; + $.each( this.history, function( index ) { + if( this != tab ) history.push( this ); + }) + this.history = history + + delete this.sessions[tab]; + this.removeDraft( tab ); + } + codiad.editor.exterminate(); + $( '#list-active-files' ).html( '' ); + $.get( this.controller + '?action=removeall' ); + }, + + close: function( path ) { + /* Notify listeners. */ + amplify.publish( 'active.onClose', path ); + + var _this = this; + var session = this.sessions[path]; + + /* Animate only if the tabThumb if a tab, not a dropdown item. */ + if( session.tabThumb.hasClass( 'tab-item' ) ) { + session.tabThumb.css( { + 'z-index': 1 + }); + session.tabThumb.animate( { + top: $( '#editor-top-bar' ).height() + 'px' + }, 300, function() { + session.tabThumb.remove(); + _this.updateTabDropdownVisibility(); + }); + } else { + session.tabThumb.remove(); + _this.updateTabDropdownVisibility(); + } + + session.listThumb.remove(); + + /* Remove closed path from history */ + var history = []; + $.each( this.history, function( index ) { + if( this != path ) history.push( this ); + }) + this.history = history + + /* Select all the tab tumbs except the one which is to be removed. */ + var tabThumbs = $( '#tab-list-active-files li[data-path!="' + path + '"]' ); + + if( tabThumbs.length == 0 ) { + codiad.editor.exterminate(); + } else { + + var nextPath = ''; + if( this.history.length > 0 ) { + nextPath = this.history[this.history.length - 1]; + } else { + nextPath = $( tabThumbs[0] ).attr( 'data-path' ); + } + var nextSession = this.sessions[nextPath]; + codiad.editor.removeSession( session, nextSession ); + + this.focus( nextPath ); + } + delete this.sessions[path]; + $.get( this.controller + '?action=remove&path=' + encodeURIComponent( path ) ); + this.removeDraft( path ); + }, + + ////////////////////////////////////////////////////////////////// + // Process rename + ////////////////////////////////////////////////////////////////// + + rename: function( oldPath, newPath ) { + var switchSessions = function( oldPath, newPath ) { + var tabThumb = this.sessions[oldPath].tabThumb; + tabThumb.attr( 'data-path', newPath ); + var title = newPath; + if( codiad.project.isAbsPath( newPath ) ) { + title = newPath.substring( 1 ); + } + tabThumb.find( '.label' ) + .text( title ); + this.sessions[newPath] = this.sessions[oldPath]; + this.sessions[newPath].path = newPath; + delete this.sessions[oldPath]; + //Rename history + for( var i = 0; i < this.history.length; i++ ) { + if( this.history[i] === oldPath ) { + this.history[i] = newPath; + } + } + }; + if( this.sessions[oldPath] ) { + // A file was renamed + switchSessions.apply( this, [oldPath, newPath] ); + // pass new sessions instance to setactive + for( var k = 0; k < codiad.editor.instances.length; k++ ) { + if( codiad.editor.instances[k].getSession().path === newPath ) { + codiad.editor.setActive( codiad.editor.instances[k] ); + } + } + + var newSession = this.sessions[newPath]; + + // Change Editor Mode + var mode = codiad.editor.selectMode( newPath ); + + // handle async mode change + var fn = function() { + codiad.editor.setModeDisplay( newSession ); + newSession.removeListener( 'changeMode', fn ); + } + + newSession.on( "changeMode", fn ); + newSession.setMode( mode.mode ); + } else { + // A folder was renamed + var newKey; + for( var key in this.sessions ) { + newKey = key.replace( oldPath, newPath ); + if( newKey !== key ) { + switchSessions.apply( this, [key, newKey] ); + } + } + } + $.get( this.controller + '?action=rename&old_path=' + encodeURIComponent( oldPath ) + '&new_path=' + encodeURIComponent( newPath ), function() { + /* Notify listeners. */ + amplify.publish( 'active.onRename', { + "oldPath": oldPath, + "newPath": newPath + }); + }); + }, + + ////////////////////////////////////////////////////////////////// + // Open in Browser + ////////////////////////////////////////////////////////////////// + + openInBrowser: function() { + var path = this.getPath(); + if( path ) { + codiad.filemanager.openInBrowser( path ); + } else { + codiad.message.error( 'No Open Files' ); + } + }, + + ////////////////////////////////////////////////////////////////// + // Get Selected Text + ////////////////////////////////////////////////////////////////// + + getSelectedText: function() { + var path = this.getPath(); + var session = this.sessions[path]; + + if( path && this.isOpen( path ) ) { + return session.getTextRange( + codiad.editor.getActive() + .getSelectionRange() ); + } else { + codiad.message.error( i18n( 'No Open Files or Selected Text' ) ); + } + }, + + ////////////////////////////////////////////////////////////////// + // Insert Text + ////////////////////////////////////////////////////////////////// + + insertText: function( val ) { + codiad.editor.getActive() + .insert( val ); + }, + + ////////////////////////////////////////////////////////////////// + // Goto Line + ////////////////////////////////////////////////////////////////// + + gotoLine: function( line ) { + codiad.editor.getActive() + .gotoLine( line, 0, true ); + }, + + ////////////////////////////////////////////////////////////////// + // Move Up (Key Combo) + ////////////////////////////////////////////////////////////////// + + move: function( dir ) { + + var num = $( '#tab-list-active-files li' ).length; + if( num === 0 ) return; + + var newActive = null; + var active = null; + + if( dir == 'up' ) { + + // If active is in the tab list + active = $( '#tab-list-active-files li.active' ); + if( active.length > 0 ) { + // Previous or rotate to the end + newActive = active.prev( 'li' ); + if( newActive.length === 0 ) { + newActive = $( '#dropdown-list-active-files li:last-child' ) + if( newActive.length === 0 ) { + newActive = $( '#tab-list-active-files li:last-child' ) + } + } + } + + // If active is in the dropdown list + active = $( '#dropdown-list-active-files li.active' ); + if( active.length > 0 ) { + // Previous + newActive = active.prev( 'li' ); + if( newActive.length === 0 ) { + newActive = $( '#tab-list-active-files li:last-child' ) + } + } + + } else { + + // If active is in the tab list + active = $( '#tab-list-active-files li.active' ); + if( active.length > 0 ) { + // Next or rotate to the beginning + newActive = active.next( 'li' ); + if( newActive.length === 0 ) { + newActive = $( '#dropdown-list-active-files li:first-child' ); + if( newActive.length === 0 ) { + newActive = $( '#tab-list-active-files li:first-child' ) + } + } + } + + // If active is in the dropdown list + active = $( '#dropdown-list-active-files li.active' ); + if( active.length > 0 ) { + // Next or rotate to the beginning + newActive = active.next( 'li' ); + if( newActive.length === 0 ) { + newActive = $( '#tab-list-active-files li:first-child' ) + } + } + + } + + if( newActive ) this.focus( newActive.attr( 'data-path' ), false ); + }, + + ////////////////////////////////////////////////////////////////// + // Dropdown Menu + ////////////////////////////////////////////////////////////////// + + initTabDropdownMenu: function() { + var _this = this; + + var menu = $( '#dropdown-list-active-files' ); + var button = $( '#tab-dropdown-button' ); + var closebutton = $( '#tab-close-button' ); + + menu.appendTo( $( 'body' ) ); + + button.click( function( e ) { + e.stopPropagation(); + _this.toggleTabDropdownMenu(); + }); + + closebutton.click( function( e ) { + e.stopPropagation(); + _this.removeAll(); + }); + }, + + showTabDropdownMenu: function() { + var menu = $( '#dropdown-list-active-files' ); + if( !menu.is( ':visible' ) ) this.toggleTabDropdownMenu(); + }, + + hideTabDropdownMenu: function() { + var menu = $( '#dropdown-list-active-files' ); + if( menu.is( ':visible' ) ) this.toggleTabDropdownMenu(); + }, + + toggleTabDropdownMenu: function() { + var _this = this; + var menu = $( '#dropdown-list-active-files' ); + + menu.css( { + top: $( "#editor-top-bar" ).height() + 'px', + right: '20px', + width: '200px' + }); + + menu.slideToggle( 'fast' ); + + if( menu.is( ':visible' ) ) { + // handle click-out autoclosing + var fn = function() { + menu.hide(); + $( window ).off( 'click', fn ) + } + $( window ).on( 'click', fn ); + } + }, + + moveTabToDropdownMenu: function( tab, prepend ) { + if( prepend === undefined ) { + prepend = false; + } + + tab.remove(); + path = tab.attr( 'data-path' ); + + var tabThumb = this.createMenuItemThumb( path ); + if( prepend ) $( '#dropdown-list-active-files' ).prepend( tabThumb ); + else $( '#dropdown-list-active-files' ).append( tabThumb ); + + if( tab.hasClass( "changed" ) ) { + tabThumb.addClass( "changed" ); + } + + if( tab.hasClass( "active" ) ) { + tabThumb.addClass( "active" ); + } + + this.sessions[path].tabThumb = tabThumb; + }, + + moveDropdownMenuItemToTab: function( menuItem, prepend ) { + if( prepend === undefined ) { + prepend = false; + } + + menuItem.remove(); + path = menuItem.attr( 'data-path' ); + + var tabThumb = this.createTabThumb( path ); + if( prepend ) $( '#tab-list-active-files' ).prepend( tabThumb ); + else $( '#tab-list-active-files' ).append( tabThumb ); + + if( menuItem.hasClass( "changed" ) ) { + tabThumb.addClass( "changed" ); + } + + if( menuItem.hasClass( "active" ) ) { + tabThumb.addClass( "active" ); + } + + this.sessions[path].tabThumb = tabThumb; + }, + + isTabListOverflowed: function( includeFictiveTab ) { + if( includeFictiveTab === undefined ) { + includeFictiveTab = false; + } + + var tabs = $( '#tab-list-active-files li' ); + var count = tabs.length + if( includeFictiveTab ) count += 1; + if( count <= 1 ) return false; + + var width = 0; + tabs.each( function( index ) { + width += $( this ).outerWidth( true ); + }) + if( includeFictiveTab ) { + width += $( tabs[tabs.length - 1] ).outerWidth( true ); + } + + /* If we subtract the width of the left side bar, of the right side + * bar handle and of the tab dropdown handle to the window width, + * do we have enough room for the tab list? Its kind of complicated + * to handle all the offsets, so afterwards we add a fixed offset + * just t be sure. */ + var lsbarWidth = $( ".sidebar-handle" ).width(); + if( codiad.sidebars.isLeftSidebarOpen ) { + lsbarWidth = $( "#sb-left" ).width(); + } + + var rsbarWidth = $( ".sidebar-handle" ).width(); + if( codiad.sidebars.isRightSidebarOpen ) { + rsbarWidth = $( "#sb-right" ).width(); + } + + var tabListWidth = $( "#tab-list-active-files" ).width(); + var dropdownWidth = $( '#tab-dropdown' ).width(); + var closeWidth = $( '#tab-close' ).width(); + var room = window.innerWidth - lsbarWidth - rsbarWidth - dropdownWidth - closeWidth - width - 30; + return ( room < 0 ); + }, + + updateTabDropdownVisibility: function() { + while( this.isTabListOverflowed() ) { + var tab = $( '#tab-list-active-files li:last-child' ); + if( tab.length == 1 ) this.moveTabToDropdownMenu( tab, true ); + else break; + } + + while( !this.isTabListOverflowed( true ) ) { + var menuItem = $( '#dropdown-list-active-files li:first-child' ); + if( menuItem.length == 1 ) this.moveDropdownMenuItemToTab( menuItem ); + else break; + } + + if( $( '#dropdown-list-active-files li' ).length > 0 ) { + $( '#tab-dropdown' ).show(); + } else { + $( '#tab-dropdown' ).hide(); + // Be sure to hide the menu if it is opened. + $( '#dropdown-list-active-files' ).hide(); + } + if( $( '#tab-list-active-files li' ).length > 1 ) { + $( '#tab-close' ).show(); + } else { + $( '#tab-close' ).hide(); + } + }, uploadPositions: function() { - $.ajax({ + $.ajax( { type: 'POST', url: codiad.active.controller + '?action=save_positions', data: { positions: ( JSON.stringify( codiad.active.positions ) ) }, - success: function( data ) { - }, + success: function( data ) {}, }); }, @@ -1025,7 +1033,7 @@ this.positions[path] = position; }, - getPosition: function( path=null, live=false ) { + getPosition: function( path = null, live = false ) { if( path === null ) { @@ -1036,7 +1044,7 @@ let position = null; let session = codiad.active.sessions[path]; - for( let i = codiad.editor.instances.length;i--; ) { + for( let i = codiad.editor.instances.length; i--; ) { if( codiad.editor.instances[i].getSession().path == path ) { @@ -1049,7 +1057,7 @@ position = editor.getCursorPosition(); } else { - if( ! this.positions[path] === undefined ) { + if( !this.positions[path] === undefined ) { position = this.positions[path].position; } @@ -1067,7 +1075,7 @@ return position; }, - setPosition: function( cursor=null ) { + setPosition: function( cursor = null ) { let editor = codiad.editor.getActive(); @@ -1079,36 +1087,36 @@ editor.scrollToLine( cursor.row, true, true, function() {}); editor.moveCursorTo( cursor.row, cursor.column ); }, - ////////////////////////////////////////////////////////////////// - // Factory - ////////////////////////////////////////////////////////////////// - - splitDirectoryAndFileName: function(path) { - var index = path.lastIndexOf('/'); - return { - fileName: path.substring(index + 1), - directory: (path.indexOf('/') == 0)? path.substring(1, index + 1):path.substring(0, index + 1) - } - }, - - createListThumb: function(path) { - return $('
  • ' + path + '
  • '); - }, - - createTabThumb: function(path) { - split = this.splitDirectoryAndFileName(path); - return $('
  • ' - + split.directory + '' + split.fileName + '' - + 'x
  • '); - }, - - createMenuItemThumb: function(path) { - split = this.splitDirectoryAndFileName(path); - return $('
  • ' - + split.directory + '' + split.fileName + '' - + '
  • '); - }, - - }; - -})(this, jQuery); + ////////////////////////////////////////////////////////////////// + // Factory + ////////////////////////////////////////////////////////////////// + + splitDirectoryAndFileName: function( path ) { + var index = path.lastIndexOf( '/' ); + return { + fileName: path.substring( index + 1 ), + directory: ( path.indexOf( '/' ) == 0 ) ? path.substring( 1, index + 1 ) : path.substring( 0, index + 1 ) + } + }, + + createListThumb: function( path ) { + return $( '
  • ' + path + '
  • ' ); + }, + + createTabThumb: function( path ) { + split = this.splitDirectoryAndFileName( path ); + return $( '
  • ' + + split.directory + '' + split.fileName + '' + + 'x
  • ' ); + }, + + createMenuItemThumb: function( path ) { + split = this.splitDirectoryAndFileName( path ); + return $( '
  • ' + + split.directory + '' + split.fileName + '' + + '
  • ' ); + }, + + }; + +})( this, jQuery ); diff --git a/components/autosave/init.js b/components/autosave/init.js index e3889f3..b45c4be 100755 --- a/components/autosave/init.js +++ b/components/autosave/init.js @@ -38,12 +38,13 @@ editor: null, invalid_states: [ "", " ", null, undefined ], path: curpath, + save_interval: null, saving: false, settings: { autosave: true, toggle: true, }, - verbose: false, + verbose: true, init: async function() { @@ -109,6 +110,10 @@ * try to close it, the program will throw an exception and * stop you from closing the file */ + if( codiad.auto_save.verbose ) { + + console.log( "Error removing event listener", e ); + } } }); @@ -128,6 +133,30 @@ }); }, + auto_save: function() { + + /** + * When saving after every change, we encounter an issue where every + * once in a while the last change or last few changes will not get + * saved. Due to this, I decided to instead only save after the user + * has finished typing their changes. + * + * On every change to the editor, we set a timeout of half a second + * to see if the user is still typing. If they are, we clear the + * timeout and set it again. If they have stopped typing then the + * timout is triggered after 500 miliseconds and the file is saved. + */ + + let _this = codiad.auto_save; + + if( _this.save_interval !== null ) { + + clearTimeout( _this.save_interval ); + _this.save_interval = null; + } + _this.save_interval = setTimeout( _this.save, 500 ); + }, + /** * * This is where the core functionality goes, any call, references, @@ -135,9 +164,15 @@ * */ - auto_save: function() { + save: function() { let _this = codiad.auto_save; + + if( _this.saving ) { + + return; + } + _this.saving = true; let tabs = document.getElementsByClassName( "tab-item" ); let path = codiad.active.getPath(); @@ -157,9 +192,9 @@ if( _this.verbose ) { - console.log( content, _this.content ); + //console.log( content, _this.content ); } - + /* if( content == _this.content ) { let session = codiad.active.sessions[path]; @@ -180,7 +215,7 @@ return; } - /* + this code caused issues even though it is the proper way to save something. Whenever in collaboration, the server constantly gave a wrong file version error. @@ -210,13 +245,6 @@ } } _this.saving = false; - - setTimeout(function() { - - //Call the function again after one second so that if we missed the last change we resave the file. - let _this = codiad.auto_save; - _this.auto_save(); - }, 1000); }, reload_interval: async function() { @@ -226,7 +254,13 @@ window.clearInterval( codiad.autosave.auto_save_trigger ); window.clearInterval( this.auto_save_trigger ); - } catch( error ) {} + } catch( error ) { + + if( codiad.auto_save.verbose ) { + + console.log( "Error clearing interval", error ); + } + } if( codiad.auto_save.settings.autosave == true || codiad.auto_save.settings.autosave == "true" ) { diff --git a/components/editor/init.js b/components/editor/init.js index aa1a700..ba78c8b 100755 --- a/components/editor/init.js +++ b/components/editor/init.js @@ -1,1603 +1,1605 @@ -/* - * Copyright (c) Codiad & Kent Safranski (codiad.com), distributed - * as-is and without warranty under the MIT License. See - * [root]/license.txt for more. This information must remain intact. - */ - -(function(global, $) { - - // Classes from Ace - var VirtualRenderer = ace.require('ace/virtual_renderer').VirtualRenderer; - var Editor = ace.require('ace/editor').Editor; - var EditSession = ace.require('ace/edit_session').EditSession; - var ModeList = ace.require("ace/ext/modelist"); - var UndoManager = ace.require("ace/undomanager").UndoManager; - - // Editor modes that have been loaded - var editorModes = {}; - - var codiad = global.codiad; - codiad._cursorPoll = null; - - var separatorWidth = 3; - - $(function(){ - codiad.editor.init(); - }); - - function SplitContainer(root, children, splitType) { - var _this = this; - - this.root = root; - this.splitType = splitType; - this.childContainers = {}; - this.childElements = {}; - this.splitProp = 0.5; - - this.setChild(0, children[0]); - this.setChild(1, children[1]); - - this.splitter = $('
    ') - .addClass('splitter') - .appendTo(root) - .draggable({ - axis: (splitType === 'horizontal' ? 'x' : 'y'), - drag: function(e, ui){ - if (_this.splitType === 'horizontal') { - var w1, w2; - w1 = ui.position.left - separatorWidth/2; - w2 = _this.root.width() - ui.position.left - - separatorWidth/2; - _this.splitProp = w1 / _this.root.width(); - _this.childElements[0] - .width(w1) - .trigger('h-resize', [true, true]); - _this.childElements[1] - .width(w2) - .css('left', w1 + separatorWidth + 'px') - .trigger('h-resize', [true, true]); - _this.splitProp = ui.position.left / _this.root.width(); - } else { - var h1, h2; - h1 = ui.position.top - separatorWidth/2; - h2 = _this.root.width() - ui.position.top - - separatorWidth/2; - _this.splitProp = h1 / _this.root.height(); - _this.childElements[0] - .height(h1) - .trigger('v-resize', [true, true]); - _this.childElements[1] - .height(h2) - .css('top', h1 + separatorWidth + 'px') - .trigger('v-resize', [true, true]); - } - } - }); - - if (splitType === 'horizontal') { - this.splitter - .addClass('h-splitter') - .width(separatorWidth) - .height(root.height()); - } else if (splitType === 'vertical') { - this.splitter - .addClass('v-splitter') - .height(separatorWidth) - .width(root.width()); - } - - this.root.on('h-resize', function(e, percolateUp, percolateDown){ - e.stopPropagation(); - if (_this.splitType === 'horizontal') { - var w1, w2; - w1 = _this.root.width() * _this.splitProp - - separatorWidth / 2; - w2 = _this.root.width() * (1 - _this.splitProp) - - separatorWidth / 2; - _this.childElements[0] - .width(w1); - _this.childElements[1] - .width(w2) - .css('left', w1 + separatorWidth); - _this.splitter.css('left', w1); - } else if (_this.splitType === 'vertical') { - var w = _this.root.width(); - _this.childElements[0] - .width(w); - _this.childElements[1] - .width(w); - _this.splitter.width(w); - } - if (percolateUp) { - _this.root.parent('.editor-wrapper') - .trigger('h-resize', [true ,false]); - } - if (! percolateDown) return; - if (_this.childContainers[0]) { - _this.childContainers[0].root - .trigger('h-resize', [false, true]); - } else if (_this.childContainers[1]) { - _this.childContainers[1].root - .trigger('h-resize', [false, true]); - } - }); - - this.root.on('v-resize', function(e, percolateUp, percolateDown){ - e.stopPropagation(); - if (_this.splitType === 'horizontal') { - var h = _this.root.height(); - _this.childElements[0] - .height(h); - _this.childElements[1] - .height(h); - _this.splitter.height(h); - } else if (_this.splitType === 'vertical') { - var h1 = _this.root.height() * _this.splitProp - - separatorWidth / 2; - var h2 = _this.root.height() * (1 - _this.splitProp) - - separatorWidth / 2; - _this.childElements[0] - .height(h1); - _this.childElements[1] - .height(h2) - .css('top', h1+separatorWidth); - _this.splitter.css('top', h1); - } - if (percolateUp) { - _this.root.parent('.editor-wrapper') - .trigger('v-resize', [true ,false]); - } - if (! percolateDown) return; - if (_this.childContainers[0]) { - _this.childContainers[0].root - .trigger('v-resize', [false, true]); - } else if (_this.childContainers[1]) { - _this.childContainers[1].root - .trigger('v-resize', [false, true]); - } - }); - - this.root - .trigger('h-resize', [false, false]) - .trigger('v-resize', [false, false]); - } - - SplitContainer.prototype = { - setChild: function(idx, el){ - - if (el instanceof SplitContainer) { - this.childElements[idx] = el.root; - this.childContainers[idx] = el; - el.idx = idx; - } else { - this.childElements[idx] = el; - } - - this.childElements[idx].appendTo(this.root); - this.cssInit(this.childElements[idx], idx); - }, - cssInit: function(el, idx){ - var props = {}; - var h1, h2, w1, w2, rh, rw; - - rh = this.root.height(); - rw = this.root.width(); - - if (this.splitType === 'horizontal') { - - w1 = rw * this.splitProp - separatorWidth / 2; - w2 = rw * (1 - this.splitProp) - separatorWidth / 2; - - if (idx === 0) { - props = { - left: 0, - width: w1, - height: rh, - top: 0 - }; - } else { - props = { - left: w1 + separatorWidth, - width: w2, - height: rh, - top: 0 - }; - } - - } else if (this.splitType === 'vertical') { - - h1 = rh * this.splitProp - separatorWidth / 2; - h2 = rh * (1 - this.splitProp) - separatorWidth / 2; - - if (idx === 0) { - props = { - top: 0, - height: h1, - width: rw, - left: 0 - }; - } else { - props = { - top: h1 + separatorWidth, - height: h2, - width: rw, - left: 0 - }; - } - - } - - el.css(props); - } - }; - - ////////////////////////////////////////////////////////////////// - // - // Editor Component for Codiad - // --------------------------- - // Manage the lifecycle of Editor instances - // - ////////////////////////////////////////////////////////////////// - - codiad.editor = { - - /// Editor instances - One instance corresponds to an editor - /// pane in the user interface. Different EditSessions - /// (ace/edit_session) - instances: [], - - /// Currently focussed editor - activeInstance: null, - - // Settings for Editor instances - settings: { - autocomplete: false, - theme: 'twilight', - fontSize: '13px', - printMargin: false, - printMarginColumn: 80, - highlightLine: true, - indentGuides: true, - wrapMode: false, - softTabs: false, - persistentModal: true, - rightSidebarTrigger: false, - fileManagerTrigger: false, - tabSize: 4 - }, - - multi_line: false, - - rootContainer: null, - - fileExtensionTextMode: {}, - - init: function(){ - this.createSplitMenu(); - this.createModeMenu(); - - var er = $('#editor-region'); - - er.on('h-resize-init', function(){ - $('#editor-region > .editor-wrapper') - .width($(this).width()) - .trigger('h-resize'); - - }).on('v-resize-init', function(){ - $('#editor-region > .editor-wrapper') - .height($(this).height()) - .trigger('v-resize'); - }); - - $("#root-editor-wrapper").live("contextmenu", function( e ) { - - // Context Menu - e.preventDefault(); - _this = codiad.editor - - if( _this.getActive() ) { - - let path = codiad.active.getPath(); - $(e.target).attr('data-path', path); - codiad.filemanager.contextMenuShow( e, path, 'editor', 'editor'); - $(this).addClass('context-menu-active'); - } - }); - - $(window).resize(function(){ - $('#editor-region') - .trigger('h-resize-init') - .trigger('v-resize-init'); - }); - - //Load settings when initially starting up that way the first editor - //we open doesn't have the default settings. - this.getSettings(); - }, - - ////////////////////////////////////////////////////////////////// - // - // Retrieve editor settings from database - // - ////////////////////////////////////////////////////////////////// - - getSettings: async function() { - - let boolVal = null; - let _this = this; - let options = [ - 'editor.fontSize', - 'editor.overScroll', - 'editor.printMarginColumn', - 'editor.tabSize', - 'editor.theme', - ]; - let bool_options = [ - 'editor.autocomplete', - 'settings.autosave', - 'editor.printMargin', - 'editor.highlightLine', - 'editor.indentGuides', - 'editor.wrapMode', - 'editor.rightSidebarTrigger', - 'editor.fileManagerTrigger', - 'editor.softTabs', - 'editor.persistentModal', - ]; - - let user_settings = await codiad.settings.get_options(); - - //console.log( user_settings ); - - $.each( options, function( idx, key ) { - - let localValue = user_settings['codiad.' + key]; - if ( localValue != null ) { - - _this.settings[key.split('.').pop()] = localValue; - } - }); - - $.each( bool_options, async function(idx, key) { - - let localValue = user_settings['codiad.' + key]; - if ( localValue != null ) { - - _this.settings[key.split('.').pop()] = (localValue == 'true'); - } - }); - }, - - ///////////////////////////////////////////////////////////////// - // - // Apply configuration settings - // - // Parameters: - // i - {Editor} - // - ///////////////////////////////////////////////////////////////// - - applySettings: function(i) { - // Check user-specified settings - this.getSettings(); - - // Apply the current configuration settings: - i.setTheme('ace/theme/' + this.settings.theme); - i.setFontSize(this.settings.fontSize); - i.setPrintMarginColumn(this.settings.printMarginColumn); - i.setShowPrintMargin(this.settings.printMargin); - i.setHighlightActiveLine(this.settings.highlightLine); - i.setDisplayIndentGuides(this.settings.indentGuides); - i.getSession().setUseWrapMode(this.settings.wrapMode); - this.setTabSize(this.settings.tabSize, i); - this.setSoftTabs(this.settings.softTabs, i); - this.setOverScroll(this.settings.overScroll, i); - i.setOptions({ - enableBasicAutocompletion: true, - enableSnippets: true, - enableLiveAutocompletion: this.settings.autocomplete - }); - }, - - ////////////////////////////////////////////////////////////////// - // - // Create a new editor instance attached to given session - // - // Parameters: - // session - {EditSession} Session to be used for new Editor instance - // - ////////////////////////////////////////////////////////////////// - - addInstance: function(session, where) { - var el = $('
    '); - var chType, chArr = [], sc = null, chIdx = null; - var _this = this; - - if (this.instances.length == 0) { - // el.appendTo($('#editor-region')); - el.appendTo($('#root-editor-wrapper')); - } else { - - var ch = this.activeInstance.el; - var root; - - chIdx = (where === 'top' || where === 'left') ? 0 : 1; - chType = (where === 'top' || where === 'bottom') ? - 'vertical' : 'horizontal'; - - chArr[chIdx] = el; - chArr[1 - chIdx] = ch; - - root = $('
    ') - .height(ch.height()) - .width(ch.width()) - .addClass('editor-wrapper-' + chType) - .appendTo(ch.parent()); - - sc = new SplitContainer(root, chArr, chType); - - if (this.instances.length > 1) { - var pContainer = this.activeInstance.splitContainer; - var idx = this.activeInstance.splitIdx; - pContainer.setChild(idx, sc); - } - } - - ace.require("ace/ext/language_tools"); - var i = ace.edit(el[0]); - var resizeEditor = function(){ - i.resize(); - }; - - if (sc) { - i.splitContainer = sc; - i.splitIdx = chIdx; - - this.activeInstance.splitContainer = sc; - this.activeInstance.splitIdx = 1 - chIdx; - - sc.root - .on('h-resize', resizeEditor) - .on('v-resize', resizeEditor); - - if (this.instances.length === 1) { - var re = function(){ - _this.instances[0].resize(); - }; - sc.root - .on('h-resize', re) - .on('v-resize', re); - } - } - - i.el = el; - this.setSession(session, i); - - this.changeListener(i); - this.cursorTracking(i); - this.clickListener(i); - this.bindKeys(i); - - this.instances.push(i); - - i.on('focus', function(){ - _this.focus(i); - }); - - return i; - }, - - createSplitMenu: function(){ - var _this = this; - var _splitOptionsMenu = $('#split-options-menu'); - - this.initMenuHandler($('#split'),_splitOptionsMenu); - - $('#split-horizontally a').click(function(e){ - e.stopPropagation(); - _this.addInstance(_this.activeInstance.getSession(), 'bottom'); - _splitOptionsMenu.hide(); - }); - - $('#split-vertically a').click(function(e){ - e.stopPropagation(); - _this.addInstance(_this.activeInstance.getSession(), 'right'); - _splitOptionsMenu.hide(); - }); - - $('#merge-all a').click(function(e){ - e.stopPropagation(); - var s = _this.activeInstance.getSession(); - _this.exterminate(); - _this.addInstance(s); - _splitOptionsMenu.hide(); - }); - }, - - createModeMenu: function(){ - var _this = this; - var _thisMenu = $('#changemode-menu'); - var modeColumns = new Array(); - var modeOptions = new Array(); - var maxOptionsColumn = 15; - var firstOption = 0; - - this.initMenuHandler($('#current-mode'),_thisMenu); - - var modes = Object.keys( ModeList.modesByName ).sort(); - - $.each(modes, function(i){ - modeOptions.push('
  • '+modes[i]+'
  • '); - }); - - var html = ''; - while(true) { - html += ''; - firstOption = firstOption + maxOptionsColumn; - if(firstOption >= modeOptions.length) { - break; - } - } - - html += '
      '; - if ((modeOptions.length-firstOption) < maxOptionsColumn) { - max = modeOptions.length; - } else { - max = firstOption + maxOptionsColumn; - } - var currentcolumn = modeOptions.slice(firstOption, max); - for (var option in currentcolumn) { - html += currentcolumn[option]; - } - html += '
    '; - _thisMenu.html(html); - - $('#changemode-menu a').click(function(e){ - e.stopPropagation(); - var newMode = "ace/mode/" + $(e.currentTarget).text(); - var actSession = _this.activeInstance.getSession(); - - // handle async mode change - var fn = function(){ - _this.setModeDisplay(actSession); - actSession.removeListener('changeMode', fn); - }; - actSession.on("changeMode", fn); - - actSession.setMode(newMode); - _thisMenu.hide(); - - }); - }, - - initMenuHandler: function(button,menu){ - var _this = this; - var thisButton = button; - var thisMenu = menu; - - thisMenu.appendTo($('body')); - - thisButton.click(function(e){ - var wh = $(window).height(); - - e.stopPropagation(); - - // close other menus - _this.closeMenus(thisMenu); - - thisMenu.css({ - // display: 'block', - bottom: ( (wh - $(this).offset().top) + 8) + 'px', - left: ($(this).offset().left - 13) + 'px' - }); - - thisMenu.slideToggle('fast'); - - // handle click-out autoclosing - var fn = function(){ - thisMenu.hide(); - $(window).off('click', fn); - }; - $(window).on('click', fn); - }); - }, - - closeMenus: function(exclude){ - var menuId = exclude.attr("id"); - if(menuId != 'split-options-menu') $('#split-options-menu').hide(); - if(menuId != 'changemode-menu') $('#changemode-menu').hide(); - }, - - setModeDisplay: function(session){ - var currMode = session.getMode().$id; - if(currMode){ - currMode = currMode.substring(currMode.lastIndexOf('/') + 1); - $('#current-mode').html(currMode); - } else { - $('#current-mode').html('text'); - } - }, - - ////////////////////////////////////////////////////////////////// - // - // Remove all Editor instances and clean up the DOM - // - ////////////////////////////////////////////////////////////////// - - exterminate: function() { - $('.editor').remove(); - $('.editor-wrapper').remove(); - $('#editor-region').append($('
    ').attr('id', 'editor')); - $('#current-file').html(''); - $('#current-mode').html(''); - this.instances = []; - this.activeInstance = null; - }, - - ////////////////////////////////////////////////////////////////// - // - // Detach EditSession session from all Editor instances replacing - // them with replacementSession - // - ////////////////////////////////////////////////////////////////// - - removeSession: function(session, replacementSession) { - for (var k = 0; k < this.instances.length; k++) { - if (this.instances[k].getSession().path === session.path) { - this.instances[k].setSession(replacementSession); - } - } - if ($('#current-file').text() === session.path) { - $('#current-file').text(replacementSession.path); - } - - this.setModeDisplay(replacementSession); - }, - - isOpen: function(session){ - for (var k = 0; k < this.instances.length; k++) { - if (this.instances[k].getSession().path === session.path) { - return true; - } - } - return false; - }, - - ///////////////////////////////////////////////////////////////// - // - // Convenience function to iterate over Editor instances - // - // Parameters: - // fn - {Function} callback called with each member as an argument - // - ///////////////////////////////////////////////////////////////// - - forEach: function(fn) { - for (var k = 0; k < this.instances.length; k++) { - fn.call(this, this.instances[k]); - } - }, - - ///////////////////////////////////////////////////////////////// - // - // Get the currently active Editor instance - // - // In a multi-pane setup this would correspond to the - // editor pane user is currently working on. - // - ///////////////////////////////////////////////////////////////// - - getActive: function() { - return this.activeInstance; - }, - - ///////////////////////////////////////////////////////////////// - // - // Set an editor instance as active - // - // Parameters: - // i - {Editor} - // - ///////////////////////////////////////////////////////////////// - - setActive: function(i) { - if (! i) return; - this.activeInstance = i; - $('#current-file').text(i.getSession().path); - this.setModeDisplay(i.getSession()); - }, - - ///////////////////////////////////////////////////////////////// - // - // Change the EditSession of Editor instance - // - // Parameters: - // session - {EditSession} - // i - {Editor} - // - ///////////////////////////////////////////////////////////////// - - setSession: function(session, i) { - i = i || this.getActive(); - if (! this.isOpen(session)) { - if (! i) { - i = this.addInstance(session); - } else { - i.setSession(session); - } - } else { - // Proxy session is required because scroll-position and - // cursor position etc. are shared among sessions. - - var proxySession = new EditSession(session.getDocument(), - session.getMode()); - proxySession.setUndoManager(new UndoManager()); - proxySession.path = session.path; - proxySession.listThumb = session.listThumb; - proxySession.tabThumb = session.tabThumb; - if (! i) { - i = this.addInstance(proxySession); - } else { - i.setSession(proxySession); - } - } - this.applySettings(i); - this.cursorTracking(i); - this.setActive(i); - }, - - ///////////////////////////////////////////////////////////////// - // - // Select file mode by extension case insensitive - // - // Parameters: - // e - {String} File extension - // - ///////////////////////////////////////////////////////////////// - - selectMode: function(e) { - if(typeof(e) != 'string'){ - return 'text'; - } - e = e.toLowerCase(); - return( ModeList.getModeForPath( e ) ); - }, - - ///////////////////////////////////////////////////////////////// - // - // Add an text mode for an extension - // - // Parameters: - // extension - {String} File Extension - // mode - {String} TextMode for this extension - // - ///////////////////////////////////////////////////////////////// - - addFileExtensionTextMode: function(extension, mode){ - if(typeof(extension) != 'string' || typeof(mode) != 'string'){ - if (console){ - console.warn('wrong usage of addFileExtensionTextMode, both parameters need to be string'); - } - return; - } - mode = mode.toLowerCase(); - this.fileExtensionTextMode[extension] = mode; - }, - - ///////////////////////////////////////////////////////////////// - // - // clear all extension-text mode joins - // - ///////////////////////////////////////////////////////////////// - - clearFileExtensionTextMode: function(){ - this.fileExtensionTextMode = {}; - }, - - ///////////////////////////////////////////////////////////////// - // - // Set the editor mode - // - // Parameters: - // m - {TextMode} mode - // i - {Editor} Editor (Defaults to active editor) - // - ///////////////////////////////////////////////////////////////// - - setMode: function(m, i) { - i = i || this.getActive(); - - // Check if mode is already loaded - if (! editorModes[m]) { - - // Load the Mode - var modeFile = 'components/editor/ace-editor/mode-' + m + '.js'; - $.loadScript(modeFile, function() { - - // Mark the mode as loaded - editorModes[m] = true; - var EditorMode = ace.require('ace/mode/' + m).Mode; - i.getSession().setMode(new EditorMode()); - }, true); - } else { - - var EditorMode = ace.require('ace/mode/' + m).Mode; - i.getSession().setMode(new EditorMode()); - - } - }, - - ///////////////////////////////////////////////////////////////// - // - // Set the editor theme - // - // Parameters: - // t - {String} theme eg. twilight, cobalt etc. - // i - {Editor} Editor instance (If omitted, Defaults to all editors) - // - // For a list of themes supported by Ace - refer : - // https://github.com/ajaxorg/ace/tree/master/lib/ace/theme - // - // TODO: Provide support for custom themes - // - ///////////////////////////////////////////////////////////////// - - setTheme: function(t, i) { - if (i) { - // If a specific instance is specified, change the theme for - // this instance - i.setTheme('ace/theme/'+ t); - } else { - // Change the theme for the existing editor instances - // and make it the default for new instances - this.settings.theme = t; - for (var k = 0; k < this.instances.length; k++) { - this.instances[k].setTheme('ace/theme/'+ t); - } - } - //Database - //codiad.settings.update_option( 'codiad.editor.theme', t ); - }, - - ///////////////////////////////////////////////////////////////// - // - // Set contents of the editor - // - // Parameters: - // c - {String} content - // i - {Editor} (Defaults to active editor) - // - ///////////////////////////////////////////////////////////////// - - setContent: function(c, i) { - i = i || this.getActive(); - i.getSession().setValue(c); - }, - - ///////////////////////////////////////////////////////////////// - // - // Set Font Size - // - // Set the font for all Editor instances and remember - // the value for Editor instances to be created in - // future - // - // Parameters: - // s - {Number} font size - // i - {Editor} Editor instance (If omitted, Defaults to all editors) - // - ///////////////////////////////////////////////////////////////// - - setFontSize: function(s, i) { - if (i) { - i.setFontSize(s); - } else { - this.settings.fontSize = s; - this.forEach(function(i) { - i.setFontSize(s); - }); - } - //Database - //codiad.settings.update_option( 'codiad.editor.fontSize', s ); - }, - - - ///////////////////////////////////////////////////////////////// - // - // Enable/disable Highlighting of active line - // - // Parameters: - // h - {Boolean} - // i - {Editor} Editor instance ( If left out, setting is - // applied to all editors ) - // - ///////////////////////////////////////////////////////////////// - - setHighlightLine: function(h, i) { - if (i) { - i.setHighlightActiveLine(h); - } else { - this.settings.highlightLine = h; - this.forEach(function(i) { - i.setHighlightActiveLine(h); - }); - } - //Database - //codiad.settings.update_option( 'codiad.editor.highlightLine', h ); - }, - - ////////////////////////////////////////////////////////////////// - // - // Show/Hide print margin indicator - // - // Parameters: - // p - {Number} print margin column - // i - {Editor} (If omitted, Defaults to all editors) - // - ////////////////////////////////////////////////////////////////// - - setPrintMargin: function(p, i) { - if (i) { - i.setShowPrintMargin(p); - } else { - this.settings.printMargin = p; - this.forEach(function(i) { - i.setShowPrintMargin(p); - }); - } - //Database - //codiad.settings.update_option( 'codiad.editor.printMargin', p ); - }, - - ////////////////////////////////////////////////////////////////// - // - // Set print margin column - // - // Parameters: - // p - {Number} print margin column - // i - {Editor} (If omitted, Defaults to all editors) - // - ////////////////////////////////////////////////////////////////// - - setPrintMarginColumn: function(p, i) { - if (i) { - i.setPrintMarginColumn(p); - } else { - this.settings.printMarginColumn = p; - this.forEach(function(i) { - i.setPrintMarginColumn(p); - }); - } - //Database - //codiad.settings.update_option( 'codiad.editor.printMarginColumn', p ); - }, - - ////////////////////////////////////////////////////////////////// - // - // Show/Hide indent guides - // - // Parameters: - // g - {Boolean} - // i - {Editor} (If omitted, Defaults to all editors) - // - ////////////////////////////////////////////////////////////////// - - setIndentGuides: function(g, i) { - if (i) { - i.setDisplayIndentGuides(g); - } else { - this.settings.indentGuides = g; - this.forEach(function(i) { - i.setDisplayIndentGuides(g); - }); - } - //Database - //codiad.settings.update_option( 'codiad.editor.indentGuides', g ); - }, - - ////////////////////////////////////////////////////////////////// - // - // Enable/Disable Code Folding - // - // Parameters: - // f - {Boolean} - // i - {Editor} (If omitted, Defaults to all editors) - // - ////////////////////////////////////////////////////////////////// - - setCodeFolding: function(f, i) { - if (i) { - i.setFoldStyle(f); - } else { - this.forEach(function(i) { - i.setFoldStyle(f); - }); - } - }, - - ////////////////////////////////////////////////////////////////// - // - // Enable/Disable Line Wrapping - // - // Parameters: - // w - {Boolean} - // i - {Editor} (If omitted, Defaults to all editors) - // - ////////////////////////////////////////////////////////////////// - - setWrapMode: function(w, i) { - if (i) { - i.getSession().setUseWrapMode(w); - } else { - this.forEach(function(i) { - i.getSession().setUseWrapMode(w); - }); - } - //Database - //codiad.settings.update_option( 'codiad.editor.wrapMode', w ); - }, - - ////////////////////////////////////////////////////////////////// - // - // Set last position of modal to be saved - // - // Parameters: - // t - {Boolean} (false for Automatic Position, true for Last Position) - // i - {Editor} (If omitted, Defaults to all editors) - // - ////////////////////////////////////////////////////////////////// - - setPersistentModal: function(t, i) { - this.settings.persistentModal = t; - //Database - //codiad.settings.update_option( 'codiad.editor.persistentModal', t ); - }, - - ////////////////////////////////////////////////////////////////// - // - // Set trigger for opening the right sidebar - // - // Parameters: - // t - {Boolean} (false for Hover, true for Click) - // i - {Editor} (If omitted, Defaults to all editors) - // - ////////////////////////////////////////////////////////////////// - - setRightSidebarTrigger: function(t, i) { - this.settings.rightSidebarTrigger = t; - //Database - //codiad.settings.update_option( 'codiad.editor.rightSidebarTrigger', t ); - }, - - ////////////////////////////////////////////////////////////////// - // - // Set trigger for clicking on the filemanager - // - // Parameters: - // t - {Boolean} (false for Hover, true for Click) - // i - {Editor} (If omitted, Defaults to all editors) - // - ////////////////////////////////////////////////////////////////// - - setFileManagerTrigger: function(t, i) { - this.settings.fileManagerTrigger = t; - //Database - //codiad.settings.update_option( 'codiad.editor.fileManagerTrigger', t ); - codiad.project.loadSide(); - }, - - - ////////////////////////////////////////////////////////////////// - // - // set Tab Size - // - // Parameters: - // s - size - // i - {Editor} (If omitted, Defaults to all editors) - // - ////////////////////////////////////////////////////////////////// - - setTabSize: function(s, i) { - if (i) { - i.getSession().setTabSize(parseInt(s)); - } else { - this.forEach(function(i) { - i.getSession().setTabSize(parseInt(s)); - }); - } - //Database - //codiad.settings.update_option( 'codiad.editor.tabSize', s ); - - }, - - ////////////////////////////////////////////////////////////////// - // - // Enable or disable Soft Tabs - // - // Parameters: - // t - true / false - // i - {Editor} (If omitted, Defaults to all editors) - // - ////////////////////////////////////////////////////////////////// - - setSoftTabs: function(t, i) { - if (i) { - i.getSession().setUseSoftTabs(t); - } else { - this.forEach(function(i) { - i.getSession().setUseSoftTabs(t); - }); - } - //Database - //codiad.settings.update_option( 'codiad.editor.softTabs', t ); - - }, - - ////////////////////////////////////////////////////////////////// - // - // Get content from editor - // - // Parameters: - // i - {Editor} (Defaults to active editor) - // - ////////////////////////////////////////////////////////////////// - - getContent: function(i) { - i = i || this.getActive(); - if (! i) return; - var content = i.getSession().getValue(); - if (!content) { - content = ' '; - } // Pass something through - return content; - }, - - ////////////////////////////////////////////////////////////////// - // - // Resize the editor - Trigger the editor to readjust its layout - // esp if the container has been resized manually. - // - // Parameters: - // i - {Editor} (Defaults to active editor) - // - ////////////////////////////////////////////////////////////////// - - resize: function(i) { - i = i || this.getActive(); - if (! i) return; - i.resize(); - }, - - ////////////////////////////////////////////////////////////////// - // - // Mark the instance as changed (in the user interface) - // upon change in the document content. - // - // Parameters: - // i - {Editor} - // - ////////////////////////////////////////////////////////////////// - - changeListener: function(i) { - - var _this = this; - i.on('change', function() { - - codiad.active.markChanged(_this.getActive().getSession().path); - codiad.active.savePosition(); - }); - }, - - clickListener: function(i) { - - var _this = this; - i.on('click', function() { - - codiad.active.savePosition(); - }); - }, - - ////////////////////////////////////////////////////////////////// - // - // Get Selected Text - // - // Parameters: - // i - {Editor} (Defaults to active editor) - // - ////////////////////////////////////////////////////////////////// - - getSelectedText: function(i) { - i = i || this.getActive(); - if (! i) return; - return i.getCopyText(); - }, - - ////////////////////////////////////////////////////////////////// - // - // Insert text - // - // Parameters: - // val - {String} Text to be inserted - // i - {Editor} (Defaults to active editor) - // - ////////////////////////////////////////////////////////////////// - - insertText: function(val, i) { - i = i || this.getActive(); - if (! i) return; - i.insert(val); - }, - - ////////////////////////////////////////////////////////////////// - // - // Move the cursor to a particular line - // - // Parameters: - // line - {Number} Line number - // i - {Editor} Editor instance - // - ////////////////////////////////////////////////////////////////// - - gotoLine: function(line, i) { - i = i || this.getActive(); - if (! i) return; - i.gotoLine(line, 0, true); - }, - - ////////////////////////////////////////////////////////////////// - // - // Focus an editor - // - // Parameters: - // i - {Editor} Editor instance (Defaults to current editor) - // - ////////////////////////////////////////////////////////////////// - - focus: function(i) { - i = i || this.getActive(); - this.setActive(i); - if (! i) return; - i.focus(); - codiad.active.focus(i.getSession().path); - this.cursorTracking(i); - }, - - ////////////////////////////////////////////////////////////////// - // - // Setup Cursor Tracking - // - // Parameters: - // i - {Editor} (Defaults to active editor) - // - ////////////////////////////////////////////////////////////////// - - cursorTracking: function(i) { - - i = i || this.getActive(); - if (! i) return; - - /** - * Update the cursor position now so that when a new file opens, - * we do not have the old cursor data. - */ - - $('#cursor-position') - .html(i18n('Ln') + ': ' - + (i.getCursorPosition().row + 1) - + ' · ' + i18n('Col') + ': ' - + i.getCursorPosition().column - ); - - //Register the changecursor function so updates continue - i.selection.on( "changeCursor", function( e ) { - - codiad.active.savePosition(); - $('#cursor-position') - .html(i18n('Ln') + ': ' - + (i.getCursorPosition().row + 1) - + ' · ' + i18n('Col') + ': ' - + i.getCursorPosition().column - ); - }); - }, - - ////////////////////////////////////////////////////////////////// - // - // Setup Key bindings - // - // Parameters: - // i - {Editor} - // - ////////////////////////////////////////////////////////////////// - - bindKeys: function(i) { - - var _this = this; - - // Find - i.commands.addCommand({ - name: 'Find', - bindKey: { - win: 'Ctrl-F', - mac: 'Command-F' - }, - exec: function(e) { - _this.openSearch('find'); - } - }); - - // Find + Replace - i.commands.addCommand({ - name: 'Replace', - bindKey: { - win: 'Ctrl-R', - mac: 'Command-R' - }, - exec: function(e) { - _this.openSearch('replace'); - } - }); - - i.commands.addCommand({ - name: 'Move Up', - bindKey: { - win: 'Ctrl-up', - mac: 'Command-up' - }, - exec: function(e) { - codiad.active.move('up'); - } - }); - - i.commands.addCommand({ - name: 'Move Down', - bindKey: { - win: 'Ctrl-down', - mac: 'Command-up' - }, - exec: function(e) { - codiad.active.move('down'); - } - }); - - }, - - ////////////////////////////////////////////////////////////////// - // - // Present the Search (Find + Replace) dialog box - // - // Parameters: - // type - {String} Optional, defaults to find. Provide 'replace' for replace dialog. - // - ////////////////////////////////////////////////////////////////// - - openSearch: function(type) { - if (this.getActive()) { - codiad.modal.load(400, - 'components/editor/dialog.php?action=search&type=' + - type); - codiad.modal.hideOverlay(); - } else { - codiad.message.error('No Open Files'); - } - }, - - ////////////////////////////////////////////////////////////////// - // - // Perform Search (Find + Replace) operation - // - // Parameters: - // action - {String} find | replace | replaceAll - // i - {Editor} Defaults to active Editor instance - // - ////////////////////////////////////////////////////////////////// - - search: function(action, i) { - i = i || this.getActive(); - if (! i) return; - if( this.multi_line ) { - - var find = $('#modal textarea[name="find"]') - .val(); - var replace = $('#modal textarea[name="replace"]') - .val(); - } else { - - var find = $('#modal input[name="find"]') - .val(); - var replace = $('#modal input[name="replace"]') - .val(); - } - switch (action) { - case 'find': - - i.find(find, { - backwards: false, - wrap: true, - caseSensitive: false, - wholeWord: false, - regExp: false - }); - - break; - - case 'replace': - - i.find(find, { - backwards: false, - wrap: true, - caseSensitive: false, - wholeWord: false, - regExp: false - }); - i.replace(replace); - - break; - - case 'replaceAll': - - i.find(find, { - backwards: false, - wrap: true, - caseSensitive: false, - wholeWord: false, - regExp: false - }); - i.replaceAll(replace); - - break; - } - }, - - ////////////////////////////////////////////////////////////////// - // - // Enable editor - // - // Parameters: - // i - {Editor} (Defaults to active editor) - // - ////////////////////////////////////////////////////////////////// - - enableEditing: function(i) { - i = i || this.getActive(); - if (! i) return; - i.textInput.setReadOnly( false ); - }, - - ////////////////////////////////////////////////////////////////// - // - // Disable editor - // - // Parameters: - // i - {Editor} (Defaults to active editor) - // - ////////////////////////////////////////////////////////////////// - - disableEditing: function(i) { - i = i || this.getActive(); - if (! i) return; - i.textInput.setReadOnly( true ); - }, - - ////////////////////////////////////////////////////////////////// - // - // Set Overscroll - // - // Parameters: - // i - {Editor} (Defaults to active editor) - // - ////////////////////////////////////////////////////////////////// - - setOverScroll: function( s, i ) { - - if (i) { - i.setOption( "scrollPastEnd", s ); - } else { - this.settings.overScroll = s; - this.forEach(function(i) { - i.setOption( "scrollPastEnd", s ); - }); - } - //Database - //codiad.settings.update_option( 'codiad.editor.overScroll', s ); - }, - - setLiveAutocomplete: function( s, i ) { - - if (i) { - i.setOptions({ - enableLiveAutocompletion: s - }); - } else { - this.settings.autocomplete = s; - this.forEach(function(i) { - i.setOptions({ - enableLiveAutocompletion: s - }); - }); - } - //Database - //codiad.settings.update_option( 'codiad.editor.autocomplete', s ); - }, - - toggleMultiLine: function( e ) { - - if( e.innerText === "Multi Line" ) { - - this.multi_line = true; - e.innerText = "Single Line"; - $('input[name="find"]').hide(); - $('textarea[name="find"]').show(); - $('textarea[name="find"]').val( $('input[name="find"]').val() ); - - $('input[name="replace"]').hide(); - $('textarea[name="replace"]').show(); - $('textarea[name="replace"]').val( $('input[name="replace"]').val() ); - } else { - - this.multi_line = false; - e.innerText = "Multi Line"; - $('input[name="find"]').show(); - $('textarea[name="find"]').hide(); - $('input[name="find"]').val( $('textarea[name="find"]').val() ); - - $('input[name="replace"]').show(); - $('textarea[name="replace"]').hide(); - $('input[name="replace"]').val( $('textarea[name="replace"]').val() ); - } - }, - - paste: function( ) { - - navigator.clipboard.readText().then(text => {codiad.editor.getActive().insert( text )}); - }, - - openSort: function() { - - if ( this.getActive() && codiad.active.getSelectedText() != "" ) { - - codiad.modal.load( 400, 'components/editor/dialog.php?action=sort' ); - codiad.modal.hideOverlay(); - } else { - - codiad.message.error('No text selected'); - } - }, - - sort: function( eol ) { - - let text = $('#modal textarea[name="sort"]').val(); - let array = text.split( eol ); - array = array.sort( codiad.editor.sort_a ); - let sorted = array.join( eol ); - - console.log( text, eol, array, sorted ); - codiad.modal.unload(); - codiad.editor.getActive().insert( sorted ); - codiad.editor.getActive().focus(); - }, - - sort_a: function( a, b ) { - - let pos = 0; - let case_sensitive = $( '#modal input[name="case_sensitive"]' ).prop( 'checked' ) - - if( ! case_sensitive ) { - - a = a.toLowerCase(); - b = b.toLowerCase(); - } - - if( a < b ) { - - pos = -1; - } else if( a > b ) { - - pos = 1; - } - - return pos; - } - }; - -})(this, jQuery); \ No newline at end of file +/* + * Copyright (c) Codiad & Kent Safranski (codiad.com), distributed + * as-is and without warranty under the MIT License. See + * [root]/license.txt for more. This information must remain intact. + */ +( function( global, $ ) { + + // Classes from Ace + var VirtualRenderer = ace.require( 'ace/virtual_renderer' ).VirtualRenderer; + var Editor = ace.require( 'ace/editor' ).Editor; + var EditSession = ace.require( 'ace/edit_session' ).EditSession; + var ModeList = ace.require( "ace/ext/modelist" ); + var UndoManager = ace.require( "ace/undomanager" ).UndoManager; + + // Editor modes that have been loaded + var editorModes = {}; + + var codiad = global.codiad; + codiad._cursorPoll = null; + + var separatorWidth = 3; + + $( function() { + codiad.editor.init(); + }); + + function SplitContainer( root, children, splitType ) { + var _this = this; + + this.root = root; + this.splitType = splitType; + this.childContainers = {}; + this.childElements = {}; + this.splitProp = 0.5; + + this.setChild( 0, children[0] ); + this.setChild( 1, children[1] ); + + this.splitter = $( '
    ' ) + .addClass( 'splitter' ) + .appendTo( root ) + .draggable( { + axis: ( splitType === 'horizontal' ? 'x' : 'y' ), + drag: function( e, ui ) { + if( _this.splitType === 'horizontal' ) { + var w1, w2; + w1 = ui.position.left - separatorWidth / 2; + w2 = _this.root.width() - ui.position.left - separatorWidth / 2; + _this.splitProp = w1 / _this.root.width(); + _this.childElements[0] + .width( w1 ) + .trigger( 'h-resize', [true, true] ); + _this.childElements[1] + .width( w2 ) + .css( 'left', w1 + separatorWidth + 'px' ) + .trigger( 'h-resize', [true, true] ); + _this.splitProp = ui.position.left / _this.root.width(); + } else { + var h1, h2; + h1 = ui.position.top - separatorWidth / 2; + h2 = _this.root.width() - ui.position.top - + separatorWidth / 2; + _this.splitProp = h1 / _this.root.height(); + _this.childElements[0] + .height( h1 ) + .trigger( 'v-resize', [true, true] ); + _this.childElements[1] + .height( h2 ) + .css( 'top', h1 + separatorWidth + 'px' ) + .trigger( 'v-resize', [true, true] ); + } + } + }); + + if( splitType === 'horizontal' ) { + this.splitter + .addClass( 'h-splitter' ) + .width( separatorWidth ) + .height( root.height() ); + } else if( splitType === 'vertical' ) { + this.splitter + .addClass( 'v-splitter' ) + .height( separatorWidth ) + .width( root.width() ); + } + + this.root.on( 'h-resize', function( e, percolateUp, percolateDown ) { + e.stopPropagation(); + if( _this.splitType === 'horizontal' ) { + var w1, w2; + w1 = _this.root.width() * _this.splitProp - + separatorWidth / 2; + w2 = _this.root.width() * ( 1 - _this.splitProp ) - + separatorWidth / 2; + _this.childElements[0] + .width( w1 ); + _this.childElements[1] + .width( w2 ) + .css( 'left', w1 + separatorWidth ); + _this.splitter.css( 'left', w1 ); + } else if( _this.splitType === 'vertical' ) { + var w = _this.root.width(); + _this.childElements[0] + .width( w ); + _this.childElements[1] + .width( w ); + _this.splitter.width( w ); + } + if( percolateUp ) { + _this.root.parent( '.editor-wrapper' ) + .trigger( 'h-resize', [true, false] ); + } + if( !percolateDown ) return; + if( _this.childContainers[0] ) { + _this.childContainers[0].root + .trigger( 'h-resize', [false, true] ); + } else if( _this.childContainers[1] ) { + _this.childContainers[1].root + .trigger( 'h-resize', [false, true] ); + } + }); + + this.root.on( 'v-resize', function( e, percolateUp, percolateDown ) { + e.stopPropagation(); + if( _this.splitType === 'horizontal' ) { + var h = _this.root.height(); + _this.childElements[0] + .height( h ); + _this.childElements[1] + .height( h ); + _this.splitter.height( h ); + } else if( _this.splitType === 'vertical' ) { + var h1 = _this.root.height() * _this.splitProp - + separatorWidth / 2; + var h2 = _this.root.height() * ( 1 - _this.splitProp ) - + separatorWidth / 2; + _this.childElements[0] + .height( h1 ); + _this.childElements[1] + .height( h2 ) + .css( 'top', h1 + separatorWidth ); + _this.splitter.css( 'top', h1 ); + } + if( percolateUp ) { + _this.root.parent( '.editor-wrapper' ) + .trigger( 'v-resize', [true, false] ); + } + if( !percolateDown ) return; + if( _this.childContainers[0] ) { + _this.childContainers[0].root + .trigger( 'v-resize', [false, true] ); + } else if( _this.childContainers[1] ) { + _this.childContainers[1].root + .trigger( 'v-resize', [false, true] ); + } + }); + + this.root + .trigger( 'h-resize', [false, false] ) + .trigger( 'v-resize', [false, false] ); + } + + SplitContainer.prototype = { + setChild: function( idx, el ) { + + if( el instanceof SplitContainer ) { + this.childElements[idx] = el.root; + this.childContainers[idx] = el; + el.idx = idx; + } else { + this.childElements[idx] = el; + } + + this.childElements[idx].appendTo( this.root ); + this.cssInit( this.childElements[idx], idx ); + }, + cssInit: function( el, idx ) { + var props = {}; + var h1, h2, w1, w2, rh, rw; + + rh = this.root.height(); + rw = this.root.width(); + + if( this.splitType === 'horizontal' ) { + + w1 = rw * this.splitProp - separatorWidth / 2; + w2 = rw * ( 1 - this.splitProp ) - separatorWidth / 2; + + if( idx === 0 ) { + props = { + left: 0, + width: w1, + height: rh, + top: 0 + }; + } else { + props = { + left: w1 + separatorWidth, + width: w2, + height: rh, + top: 0 + }; + } + + } else if( this.splitType === 'vertical' ) { + + h1 = rh * this.splitProp - separatorWidth / 2; + h2 = rh * ( 1 - this.splitProp ) - separatorWidth / 2; + + if( idx === 0 ) { + props = { + top: 0, + height: h1, + width: rw, + left: 0 + }; + } else { + props = { + top: h1 + separatorWidth, + height: h2, + width: rw, + left: 0 + }; + } + + } + + el.css( props ); + } + }; + + ////////////////////////////////////////////////////////////////// + // + // Editor Component for Codiad + // --------------------------- + // Manage the lifecycle of Editor instances + // + ////////////////////////////////////////////////////////////////// + + codiad.editor = { + + /// Editor instances - One instance corresponds to an editor + /// pane in the user interface. Different EditSessions + /// (ace/edit_session) + instances: [], + + /// Currently focussed editor + activeInstance: null, + + // Settings for Editor instances + settings: { + autocomplete: false, + theme: 'twilight', + fontSize: '13px', + printMargin: false, + printMarginColumn: 80, + highlightLine: true, + indentGuides: true, + wrapMode: false, + softTabs: false, + persistentModal: true, + rightSidebarTrigger: false, + fileManagerTrigger: false, + tabSize: 4 + }, + + multi_line: false, + + rootContainer: null, + + fileExtensionTextMode: {}, + + init: function() { + this.createSplitMenu(); + this.createModeMenu(); + + var er = $( '#editor-region' ); + + er.on( 'h-resize-init', function() { + $( '#editor-region > .editor-wrapper' ) + .width( $( this ).width() ) + .trigger( 'h-resize' ); + + }).on( 'v-resize-init', function() { + $( '#editor-region > .editor-wrapper' ) + .height( $( this ).height() ) + .trigger( 'v-resize' ); + }); + + $( "#root-editor-wrapper" ).live( "contextmenu", function( e ) { + + // Context Menu + e.preventDefault(); + _this = codiad.editor + + if( _this.getActive() ) { + + let path = codiad.active.getPath(); + $( e.target ).attr( 'data-path', path ); + codiad.filemanager.contextMenuShow( e, path, 'editor', 'editor' ); + $( this ).addClass( 'context-menu-active' ); + } + }); + + $( window ).resize( function() { + $( '#editor-region' ) + .trigger( 'h-resize-init' ) + .trigger( 'v-resize-init' ); + }); + + //Load settings when initially starting up that way the first editor + //we open doesn't have the default settings. + this.getSettings(); + }, + + ////////////////////////////////////////////////////////////////// + // + // Retrieve editor settings from database + // + ////////////////////////////////////////////////////////////////// + + getSettings: async function() { + + let boolVal = null; + let _this = this; + let options = [ + 'editor.fontSize', + 'editor.overScroll', + 'editor.printMarginColumn', + 'editor.tabSize', + 'editor.theme', + ]; + let bool_options = [ + 'editor.autocomplete', + 'settings.autosave', + 'editor.printMargin', + 'editor.highlightLine', + 'editor.indentGuides', + 'editor.wrapMode', + 'editor.rightSidebarTrigger', + 'editor.fileManagerTrigger', + 'editor.softTabs', + 'editor.persistentModal', + ]; + + let user_settings = await codiad.settings.get_options(); + + //console.log( user_settings ); + + $.each( options, function( idx, key ) { + + let localValue = user_settings['codiad.' + key]; + if( localValue != null ) { + + _this.settings[key.split( '.' ).pop()] = localValue; + } + }); + + $.each( bool_options, async function( idx, key ) { + + let localValue = user_settings['codiad.' + key]; + if( localValue != null ) { + + _this.settings[key.split( '.' ).pop()] = ( localValue == 'true' ); + } + }); + }, + + ///////////////////////////////////////////////////////////////// + // + // Apply configuration settings + // + // Parameters: + // i - {Editor} + // + ///////////////////////////////////////////////////////////////// + + applySettings: function( i ) { + // Check user-specified settings + this.getSettings(); + + // Apply the current configuration settings: + i.setTheme( 'ace/theme/' + this.settings.theme ); + i.setFontSize( this.settings.fontSize ); + i.setPrintMarginColumn( this.settings.printMarginColumn ); + i.setShowPrintMargin( this.settings.printMargin ); + i.setHighlightActiveLine( this.settings.highlightLine ); + i.setDisplayIndentGuides( this.settings.indentGuides ); + i.getSession().setUseWrapMode( this.settings.wrapMode ); + this.setTabSize( this.settings.tabSize, i ); + this.setSoftTabs( this.settings.softTabs, i ); + this.setOverScroll( this.settings.overScroll, i ); + i.setOptions( { + enableBasicAutocompletion: true, + enableSnippets: true, + enableLiveAutocompletion: this.settings.autocomplete + }); + }, + + ////////////////////////////////////////////////////////////////// + // + // Create a new editor instance attached to given session + // + // Parameters: + // session - {EditSession} Session to be used for new Editor instance + // + ////////////////////////////////////////////////////////////////// + + addInstance: function( session, where ) { + var el = $( '
    ' ); + var chType, chArr = [], + sc = null, + chIdx = null; + var _this = this; + + if( this.instances.length == 0 ) { + // el.appendTo($('#editor-region')); + el.appendTo( $( '#root-editor-wrapper' ) ); + } else { + + var ch = this.activeInstance.el; + var root; + + chIdx = ( where === 'top' || where === 'left' ) ? 0 : 1; + chType = ( where === 'top' || where === 'bottom' ) ? + 'vertical' : 'horizontal'; + + chArr[chIdx] = el; + chArr[1 - chIdx] = ch; + + root = $( '
    ' ) + .height( ch.height() ) + .width( ch.width() ) + .addClass( 'editor-wrapper-' + chType ) + .appendTo( ch.parent() ); + + sc = new SplitContainer( root, chArr, chType ); + + if( this.instances.length > 1 ) { + var pContainer = this.activeInstance.splitContainer; + var idx = this.activeInstance.splitIdx; + pContainer.setChild( idx, sc ); + } + } + + ace.require( "ace/ext/language_tools" ); + var i = ace.edit( el[0] ); + var resizeEditor = function() { + i.resize(); + }; + + if( sc ) { + i.splitContainer = sc; + i.splitIdx = chIdx; + + this.activeInstance.splitContainer = sc; + this.activeInstance.splitIdx = 1 - chIdx; + + sc.root + .on( 'h-resize', resizeEditor ) + .on( 'v-resize', resizeEditor ); + + if( this.instances.length === 1 ) { + var re = function() { + _this.instances[0].resize(); + }; + sc.root + .on( 'h-resize', re ) + .on( 'v-resize', re ); + } + } + + i.el = el; + this.setSession( session, i ); + + this.changeListener( i ); + this.cursorTracking( i ); + this.clickListener( i ); + this.bindKeys( i ); + + this.instances.push( i ); + + i.on( 'focus', function() { + _this.focus( i ); + }); + + return i; + }, + + createSplitMenu: function() { + var _this = this; + var _splitOptionsMenu = $( '#split-options-menu' ); + + this.initMenuHandler( $( '#split' ), _splitOptionsMenu ); + + $( '#split-horizontally a' ).click( function( e ) { + e.stopPropagation(); + _this.addInstance( _this.activeInstance.getSession(), 'bottom' ); + _splitOptionsMenu.hide(); + }); + + $( '#split-vertically a' ).click( function( e ) { + e.stopPropagation(); + _this.addInstance( _this.activeInstance.getSession(), 'right' ); + _splitOptionsMenu.hide(); + }); + + $( '#merge-all a' ).click( function( e ) { + e.stopPropagation(); + var s = _this.activeInstance.getSession(); + _this.exterminate(); + _this.addInstance( s ); + _splitOptionsMenu.hide(); + }); + }, + + createModeMenu: function() { + var _this = this; + var _thisMenu = $( '#changemode-menu' ); + var modeColumns = new Array(); + var modeOptions = new Array(); + var maxOptionsColumn = 15; + var firstOption = 0; + + this.initMenuHandler( $( '#current-mode' ), _thisMenu ); + + var modes = Object.keys( ModeList.modesByName ).sort(); + + $.each( modes, function( i ) { + modeOptions.push( '
  • ' + modes[i] + '
  • ' ); + }); + + var html = ''; + while( true ) { + html += ''; + firstOption = firstOption + maxOptionsColumn; + if( firstOption >= modeOptions.length ) { + break; + } + } + + html += '
      '; + if( ( modeOptions.length - firstOption ) < maxOptionsColumn ) { + max = modeOptions.length; + } else { + max = firstOption + maxOptionsColumn; + } + var currentcolumn = modeOptions.slice( firstOption, max ); + for( var option in currentcolumn ) { + html += currentcolumn[option]; + } + html += '
    '; + _thisMenu.html( html ); + + $( '#changemode-menu a' ).click( function( e ) { + e.stopPropagation(); + var newMode = "ace/mode/" + $( e.currentTarget ).text(); + var actSession = _this.activeInstance.getSession(); + + // handle async mode change + var fn = function() { + _this.setModeDisplay( actSession ); + actSession.removeListener( 'changeMode', fn ); + }; + actSession.on( "changeMode", fn ); + + actSession.setMode( newMode ); + _thisMenu.hide(); + + }); + }, + + initMenuHandler: function( button, menu ) { + var _this = this; + var thisButton = button; + var thisMenu = menu; + + thisMenu.appendTo( $( 'body' ) ); + + thisButton.click( function( e ) { + var wh = $( window ).height(); + + e.stopPropagation(); + + // close other menus + _this.closeMenus( thisMenu ); + + thisMenu.css( { + // display: 'block', + bottom: ( ( wh - $( this ).offset().top ) + 8 ) + 'px', + left: ( $( this ).offset().left - 13 ) + 'px' + }); + + thisMenu.slideToggle( 'fast' ); + + // handle click-out autoclosing + var fn = function() { + thisMenu.hide(); + $( window ).off( 'click', fn ); + }; + $( window ).on( 'click', fn ); + }); + }, + + closeMenus: function( exclude ) { + var menuId = exclude.attr( "id" ); + if( menuId != 'split-options-menu' ) $( '#split-options-menu' ).hide(); + if( menuId != 'changemode-menu' ) $( '#changemode-menu' ).hide(); + }, + + setModeDisplay: function( session ) { + var currMode = session.getMode().$id; + if( currMode ) { + currMode = currMode.substring( currMode.lastIndexOf( '/' ) + 1 ); + $( '#current-mode' ).html( currMode ); + } else { + $( '#current-mode' ).html( 'text' ); + } + }, + + ////////////////////////////////////////////////////////////////// + // + // Remove all Editor instances and clean up the DOM + // + ////////////////////////////////////////////////////////////////// + + exterminate: function() { + $( '.editor' ).remove(); + $( '.editor-wrapper' ).remove(); + $( '#editor-region' ).append( $( '
    ' ).attr( 'id', 'editor' ) ); + $( '#current-file' ).html( '' ); + $( '#current-mode' ).html( '' ); + this.instances = []; + this.activeInstance = null; + }, + + ////////////////////////////////////////////////////////////////// + // + // Detach EditSession session from all Editor instances replacing + // them with replacementSession + // + ////////////////////////////////////////////////////////////////// + + removeSession: function( session, replacementSession ) { + for( var k = 0; k < this.instances.length; k++ ) { + if( this.instances[k].getSession().path === session.path ) { + this.instances[k].setSession( replacementSession ); + } + } + if( $( '#current-file' ).text() === session.path ) { + $( '#current-file' ).text( replacementSession.path ); + } + + this.setModeDisplay( replacementSession ); + }, + + isOpen: function( session ) { + for( var k = 0; k < this.instances.length; k++ ) { + if( this.instances[k].getSession().path === session.path ) { + return true; + } + } + return false; + }, + + ///////////////////////////////////////////////////////////////// + // + // Convenience function to iterate over Editor instances + // + // Parameters: + // fn - {Function} callback called with each member as an argument + // + ///////////////////////////////////////////////////////////////// + + forEach: function( fn ) { + for( var k = 0; k < this.instances.length; k++ ) { + fn.call( this, this.instances[k] ); + } + }, + + ///////////////////////////////////////////////////////////////// + // + // Get the currently active Editor instance + // + // In a multi-pane setup this would correspond to the + // editor pane user is currently working on. + // + ///////////////////////////////////////////////////////////////// + + getActive: function() { + return this.activeInstance; + }, + + ///////////////////////////////////////////////////////////////// + // + // Set an editor instance as active + // + // Parameters: + // i - {Editor} + // + ///////////////////////////////////////////////////////////////// + + setActive: function( i ) { + if( !i ) return; + this.activeInstance = i; + $( '#current-file' ).text( i.getSession().path ); + this.setModeDisplay( i.getSession() ); + }, + + ///////////////////////////////////////////////////////////////// + // + // Change the EditSession of Editor instance + // + // Parameters: + // session - {EditSession} + // i - {Editor} + // + ///////////////////////////////////////////////////////////////// + + setSession: function( session, i ) { + i = i || this.getActive(); + if( !this.isOpen( session ) ) { + if( !i ) { + i = this.addInstance( session ); + } else { + i.setSession( session ); + } + } else { + // Proxy session is required because scroll-position and + // cursor position etc. are shared among sessions. + + var proxySession = new EditSession( session.getDocument(), + session.getMode() ); + proxySession.setUndoManager( new UndoManager() ); + proxySession.path = session.path; + proxySession.listThumb = session.listThumb; + proxySession.tabThumb = session.tabThumb; + if( !i ) { + i = this.addInstance( proxySession ); + } else { + i.setSession( proxySession ); + } + } + this.applySettings( i ); + this.cursorTracking( i ); + this.setActive( i ); + }, + + ///////////////////////////////////////////////////////////////// + // + // Select file mode by extension case insensitive + // + // Parameters: + // e - {String} File extension + // + ///////////////////////////////////////////////////////////////// + + selectMode: function( e ) { + if( typeof( e ) != 'string' ) { + return 'text'; + } + e = e.toLowerCase(); + return ( ModeList.getModeForPath( e ) ); + }, + + ///////////////////////////////////////////////////////////////// + // + // Add an text mode for an extension + // + // Parameters: + // extension - {String} File Extension + // mode - {String} TextMode for this extension + // + ///////////////////////////////////////////////////////////////// + + addFileExtensionTextMode: function( extension, mode ) { + if( typeof( extension ) != 'string' || typeof( mode ) != 'string' ) { + if( console ) { + console.warn( 'wrong usage of addFileExtensionTextMode, both parameters need to be string' ); + } + return; + } + mode = mode.toLowerCase(); + this.fileExtensionTextMode[extension] = mode; + }, + + ///////////////////////////////////////////////////////////////// + // + // clear all extension-text mode joins + // + ///////////////////////////////////////////////////////////////// + + clearFileExtensionTextMode: function() { + this.fileExtensionTextMode = {}; + }, + + ///////////////////////////////////////////////////////////////// + // + // Set the editor mode + // + // Parameters: + // m - {TextMode} mode + // i - {Editor} Editor (Defaults to active editor) + // + ///////////////////////////////////////////////////////////////// + + setMode: function( m, i ) { + i = i || this.getActive(); + + // Check if mode is already loaded + if( !editorModes[m] ) { + + // Load the Mode + var modeFile = 'components/editor/ace-editor/mode-' + m + '.js'; + $.loadScript( modeFile, function() { + + // Mark the mode as loaded + editorModes[m] = true; + var EditorMode = ace.require( 'ace/mode/' + m ).Mode; + i.getSession().setMode( new EditorMode() ); + }, true ); + } else { + + var EditorMode = ace.require( 'ace/mode/' + m ).Mode; + i.getSession().setMode( new EditorMode() ); + + } + }, + + ///////////////////////////////////////////////////////////////// + // + // Set the editor theme + // + // Parameters: + // t - {String} theme eg. twilight, cobalt etc. + // i - {Editor} Editor instance (If omitted, Defaults to all editors) + // + // For a list of themes supported by Ace - refer : + // https://github.com/ajaxorg/ace/tree/master/lib/ace/theme + // + // TODO: Provide support for custom themes + // + ///////////////////////////////////////////////////////////////// + + setTheme: function( t, i ) { + if( i ) { + // If a specific instance is specified, change the theme for + // this instance + i.setTheme( 'ace/theme/' + t ); + } else { + // Change the theme for the existing editor instances + // and make it the default for new instances + this.settings.theme = t; + for( var k = 0; k < this.instances.length; k++ ) { + this.instances[k].setTheme( 'ace/theme/' + t ); + } + } + //Database + //codiad.settings.update_option( 'codiad.editor.theme', t ); + }, + + ///////////////////////////////////////////////////////////////// + // + // Set contents of the editor + // + // Parameters: + // c - {String} content + // i - {Editor} (Defaults to active editor) + // + ///////////////////////////////////////////////////////////////// + + setContent: function( c, i ) { + i = i || this.getActive(); + i.getSession().setValue( c ); + }, + + ///////////////////////////////////////////////////////////////// + // + // Set Font Size + // + // Set the font for all Editor instances and remember + // the value for Editor instances to be created in + // future + // + // Parameters: + // s - {Number} font size + // i - {Editor} Editor instance (If omitted, Defaults to all editors) + // + ///////////////////////////////////////////////////////////////// + + setFontSize: function( s, i ) { + if( i ) { + i.setFontSize( s ); + } else { + this.settings.fontSize = s; + this.forEach( function( i ) { + i.setFontSize( s ); + }); + } + //Database + //codiad.settings.update_option( 'codiad.editor.fontSize', s ); + }, + + + ///////////////////////////////////////////////////////////////// + // + // Enable/disable Highlighting of active line + // + // Parameters: + // h - {Boolean} + // i - {Editor} Editor instance ( If left out, setting is + // applied to all editors ) + // + ///////////////////////////////////////////////////////////////// + + setHighlightLine: function( h, i ) { + if( i ) { + i.setHighlightActiveLine( h ); + } else { + this.settings.highlightLine = h; + this.forEach( function( i ) { + i.setHighlightActiveLine( h ); + }); + } + //Database + //codiad.settings.update_option( 'codiad.editor.highlightLine', h ); + }, + + ////////////////////////////////////////////////////////////////// + // + // Show/Hide print margin indicator + // + // Parameters: + // p - {Number} print margin column + // i - {Editor} (If omitted, Defaults to all editors) + // + ////////////////////////////////////////////////////////////////// + + setPrintMargin: function( p, i ) { + if( i ) { + i.setShowPrintMargin( p ); + } else { + this.settings.printMargin = p; + this.forEach( function( i ) { + i.setShowPrintMargin( p ); + }); + } + //Database + //codiad.settings.update_option( 'codiad.editor.printMargin', p ); + }, + + ////////////////////////////////////////////////////////////////// + // + // Set print margin column + // + // Parameters: + // p - {Number} print margin column + // i - {Editor} (If omitted, Defaults to all editors) + // + ////////////////////////////////////////////////////////////////// + + setPrintMarginColumn: function( p, i ) { + if( i ) { + i.setPrintMarginColumn( p ); + } else { + this.settings.printMarginColumn = p; + this.forEach( function( i ) { + i.setPrintMarginColumn( p ); + }); + } + //Database + //codiad.settings.update_option( 'codiad.editor.printMarginColumn', p ); + }, + + ////////////////////////////////////////////////////////////////// + // + // Show/Hide indent guides + // + // Parameters: + // g - {Boolean} + // i - {Editor} (If omitted, Defaults to all editors) + // + ////////////////////////////////////////////////////////////////// + + setIndentGuides: function( g, i ) { + if( i ) { + i.setDisplayIndentGuides( g ); + } else { + this.settings.indentGuides = g; + this.forEach( function( i ) { + i.setDisplayIndentGuides( g ); + }); + } + //Database + //codiad.settings.update_option( 'codiad.editor.indentGuides', g ); + }, + + ////////////////////////////////////////////////////////////////// + // + // Enable/Disable Code Folding + // + // Parameters: + // f - {Boolean} + // i - {Editor} (If omitted, Defaults to all editors) + // + ////////////////////////////////////////////////////////////////// + + setCodeFolding: function( f, i ) { + if( i ) { + i.setFoldStyle( f ); + } else { + this.forEach( function( i ) { + i.setFoldStyle( f ); + }); + } + }, + + ////////////////////////////////////////////////////////////////// + // + // Enable/Disable Line Wrapping + // + // Parameters: + // w - {Boolean} + // i - {Editor} (If omitted, Defaults to all editors) + // + ////////////////////////////////////////////////////////////////// + + setWrapMode: function( w, i ) { + if( i ) { + i.getSession().setUseWrapMode( w ); + } else { + this.forEach( function( i ) { + i.getSession().setUseWrapMode( w ); + }); + } + //Database + //codiad.settings.update_option( 'codiad.editor.wrapMode', w ); + }, + + ////////////////////////////////////////////////////////////////// + // + // Set last position of modal to be saved + // + // Parameters: + // t - {Boolean} (false for Automatic Position, true for Last Position) + // i - {Editor} (If omitted, Defaults to all editors) + // + ////////////////////////////////////////////////////////////////// + + setPersistentModal: function( t, i ) { + this.settings.persistentModal = t; + //Database + //codiad.settings.update_option( 'codiad.editor.persistentModal', t ); + }, + + ////////////////////////////////////////////////////////////////// + // + // Set trigger for opening the right sidebar + // + // Parameters: + // t - {Boolean} (false for Hover, true for Click) + // i - {Editor} (If omitted, Defaults to all editors) + // + ////////////////////////////////////////////////////////////////// + + setRightSidebarTrigger: function( t, i ) { + this.settings.rightSidebarTrigger = t; + //Database + //codiad.settings.update_option( 'codiad.editor.rightSidebarTrigger', t ); + }, + + ////////////////////////////////////////////////////////////////// + // + // Set trigger for clicking on the filemanager + // + // Parameters: + // t - {Boolean} (false for Hover, true for Click) + // i - {Editor} (If omitted, Defaults to all editors) + // + ////////////////////////////////////////////////////////////////// + + setFileManagerTrigger: function( t, i ) { + this.settings.fileManagerTrigger = t; + //Database + //codiad.settings.update_option( 'codiad.editor.fileManagerTrigger', t ); + codiad.project.loadSide(); + }, + + + ////////////////////////////////////////////////////////////////// + // + // set Tab Size + // + // Parameters: + // s - size + // i - {Editor} (If omitted, Defaults to all editors) + // + ////////////////////////////////////////////////////////////////// + + setTabSize: function( s, i ) { + if( i ) { + i.getSession().setTabSize( parseInt( s ) ); + } else { + this.forEach( function( i ) { + i.getSession().setTabSize( parseInt( s ) ); + }); + } + //Database + //codiad.settings.update_option( 'codiad.editor.tabSize', s ); + + }, + + ////////////////////////////////////////////////////////////////// + // + // Enable or disable Soft Tabs + // + // Parameters: + // t - true / false + // i - {Editor} (If omitted, Defaults to all editors) + // + ////////////////////////////////////////////////////////////////// + + setSoftTabs: function( t, i ) { + if( i ) { + i.getSession().setUseSoftTabs( t ); + } else { + this.forEach( function( i ) { + i.getSession().setUseSoftTabs( t ); + }); + } + //Database + //codiad.settings.update_option( 'codiad.editor.softTabs', t ); + + }, + + ////////////////////////////////////////////////////////////////// + // + // Get content from editor + // + // Parameters: + // i - {Editor} (Defaults to active editor) + // + ////////////////////////////////////////////////////////////////// + + getContent: function( i ) { + i = i || this.getActive(); + if( !i ) return; + var content = i.getSession().getValue(); + if( !content ) { + content = ' '; + } // Pass something through + return content; + }, + + ////////////////////////////////////////////////////////////////// + // + // Resize the editor - Trigger the editor to readjust its layout + // esp if the container has been resized manually. + // + // Parameters: + // i - {Editor} (Defaults to active editor) + // + ////////////////////////////////////////////////////////////////// + + resize: function( i ) { + i = i || this.getActive(); + if( !i ) return; + i.resize(); + }, + + ////////////////////////////////////////////////////////////////// + // + // Mark the instance as changed (in the user interface) + // upon change in the document content. + // + // Parameters: + // i - {Editor} + // + ////////////////////////////////////////////////////////////////// + + changeListener: function( i ) { + + var _this = this; + i.on( 'change', function() { + + codiad.active.markChanged( _this.getActive().getSession().path ); + codiad.active.savePosition(); + }); + }, + + clickListener: function( i ) { + + var _this = this; + i.on( 'click', function() { + + codiad.active.savePosition(); + }); + }, + + ////////////////////////////////////////////////////////////////// + // + // Get Selected Text + // + // Parameters: + // i - {Editor} (Defaults to active editor) + // + ////////////////////////////////////////////////////////////////// + + getSelectedText: function( i ) { + i = i || this.getActive(); + if( !i ) return; + return i.getCopyText(); + }, + + ////////////////////////////////////////////////////////////////// + // + // Insert text + // + // Parameters: + // val - {String} Text to be inserted + // i - {Editor} (Defaults to active editor) + // + ////////////////////////////////////////////////////////////////// + + insertText: function( val, i ) { + i = i || this.getActive(); + if( !i ) return; + i.insert( val ); + }, + + ////////////////////////////////////////////////////////////////// + // + // Move the cursor to a particular line + // + // Parameters: + // line - {Number} Line number + // i - {Editor} Editor instance + // + ////////////////////////////////////////////////////////////////// + + gotoLine: function( line, i ) { + i = i || this.getActive(); + if( !i ) return; + i.gotoLine( line, 0, true ); + }, + + ////////////////////////////////////////////////////////////////// + // + // Focus an editor + // + // Parameters: + // i - {Editor} Editor instance (Defaults to current editor) + // + ////////////////////////////////////////////////////////////////// + + focus: function( i ) { + i = i || this.getActive(); + this.setActive( i ); + if( !i ) return; + i.focus(); + codiad.active.focus( i.getSession().path ); + this.cursorTracking( i ); + }, + + ////////////////////////////////////////////////////////////////// + // + // Setup Cursor Tracking + // + // Parameters: + // i - {Editor} (Defaults to active editor) + // + ////////////////////////////////////////////////////////////////// + + cursorTracking: function( i ) { + + i = i || this.getActive(); + if( !i ) return; + + /** + * Update the cursor position now so that when a new file opens, + * we do not have the old cursor data. + */ + + $( '#cursor-position' ) + .html( i18n( 'Ln' ) + ': ' + + ( i.getCursorPosition().row + 1 ) + + ' · ' + i18n( 'Col' ) + ': ' + + i.getCursorPosition().column + ); + + //Register the changecursor function so updates continue + i.selection.on( "changeCursor", function( e ) { + + codiad.active.savePosition(); + $( '#cursor-position' ) + .html( i18n( 'Ln' ) + ': ' + + ( i.getCursorPosition().row + 1 ) + + ' · ' + i18n( 'Col' ) + ': ' + + i.getCursorPosition().column + ); + }); + }, + + ////////////////////////////////////////////////////////////////// + // + // Setup Key bindings + // + // Parameters: + // i - {Editor} + // + ////////////////////////////////////////////////////////////////// + + bindKeys: function( i ) { + + var _this = this; + + // Find + i.commands.addCommand( { + name: 'Find', + bindKey: { + win: 'Ctrl-F', + mac: 'Command-F' + }, + exec: function( e ) { + _this.openSearch( 'find' ); + } + }); + + // Find + Replace + i.commands.addCommand( { + name: 'Replace', + bindKey: { + win: 'Ctrl-R', + mac: 'Command-R' + }, + exec: function( e ) { + _this.openSearch( 'replace' ); + } + }); + + i.commands.addCommand( { + name: 'Move Up', + bindKey: { + win: 'Ctrl-up', + mac: 'Command-up' + }, + exec: function( e ) { + codiad.active.move( 'up' ); + } + }); + + i.commands.addCommand( { + name: 'Move Down', + bindKey: { + win: 'Ctrl-down', + mac: 'Command-up' + }, + exec: function( e ) { + codiad.active.move( 'down' ); + } + }); + + }, + + ////////////////////////////////////////////////////////////////// + // + // Present the Search (Find + Replace) dialog box + // + // Parameters: + // type - {String} Optional, defaults to find. Provide 'replace' for replace dialog. + // + ////////////////////////////////////////////////////////////////// + + openSearch: function( type ) { + if( this.getActive() ) { + codiad.modal.load( 400, + 'components/editor/dialog.php?action=search&type=' + + type ); + codiad.modal.hideOverlay(); + } else { + codiad.message.error( 'No Open Files' ); + } + }, + + ////////////////////////////////////////////////////////////////// + // + // Perform Search (Find + Replace) operation + // + // Parameters: + // action - {String} find | replace | replaceAll + // i - {Editor} Defaults to active Editor instance + // + ////////////////////////////////////////////////////////////////// + + search: function( action, i ) { + i = i || this.getActive(); + if( !i ) return; + if( this.multi_line ) { + + var find = $( '#modal textarea[name="find"]' ) + .val(); + var replace = $( '#modal textarea[name="replace"]' ) + .val(); + } else { + + var find = $( '#modal input[name="find"]' ) + .val(); + var replace = $( '#modal input[name="replace"]' ) + .val(); + } + switch ( action ) { + case 'find': + + i.find( find, { + backwards: false, + wrap: true, + caseSensitive: false, + wholeWord: false, + regExp: false + }); + + break; + + case 'replace': + + i.find( find, { + backwards: false, + wrap: true, + caseSensitive: false, + wholeWord: false, + regExp: false + }); + i.replace( replace ); + + break; + + case 'replaceAll': + + i.find( find, { + backwards: false, + wrap: true, + caseSensitive: false, + wholeWord: false, + regExp: false + }); + i.replaceAll( replace ); + + break; + } + }, + + ////////////////////////////////////////////////////////////////// + // + // Enable editor + // + // Parameters: + // i - {Editor} (Defaults to active editor) + // + ////////////////////////////////////////////////////////////////// + + enableEditing: function( i ) { + i = i || this.getActive(); + if( !i ) return; + i.textInput.setReadOnly( false ); + }, + + ////////////////////////////////////////////////////////////////// + // + // Disable editor + // + // Parameters: + // i - {Editor} (Defaults to active editor) + // + ////////////////////////////////////////////////////////////////// + + disableEditing: function( i ) { + i = i || this.getActive(); + if( !i ) return; + i.textInput.setReadOnly( true ); + }, + + ////////////////////////////////////////////////////////////////// + // + // Set Overscroll + // + // Parameters: + // i - {Editor} (Defaults to active editor) + // + ////////////////////////////////////////////////////////////////// + + setOverScroll: function( s, i ) { + + if( i ) { + i.setOption( "scrollPastEnd", s ); + } else { + this.settings.overScroll = s; + this.forEach( function( i ) { + i.setOption( "scrollPastEnd", s ); + }); + } + //Database + //codiad.settings.update_option( 'codiad.editor.overScroll', s ); + }, + + setLiveAutocomplete: function( s, i ) { + + if( i ) { + i.setOptions( { + enableLiveAutocompletion: s + }); + } else { + this.settings.autocomplete = s; + this.forEach( function( i ) { + i.setOptions( { + enableLiveAutocompletion: s + }); + }); + } + //Database + //codiad.settings.update_option( 'codiad.editor.autocomplete', s ); + }, + + toggleMultiLine: function( e ) { + + if( e.innerText === "Multi Line" ) { + + this.multi_line = true; + e.innerText = "Single Line"; + $( 'input[name="find"]' ).hide(); + $( 'textarea[name="find"]' ).show(); + $( 'textarea[name="find"]' ).val( $( 'input[name="find"]' ).val() ); + + $( 'input[name="replace"]' ).hide(); + $( 'textarea[name="replace"]' ).show(); + $( 'textarea[name="replace"]' ).val( $( 'input[name="replace"]' ).val() ); + } else { + + this.multi_line = false; + e.innerText = "Multi Line"; + $( 'input[name="find"]' ).show(); + $( 'textarea[name="find"]' ).hide(); + $( 'input[name="find"]' ).val( $( 'textarea[name="find"]' ).val() ); + + $( 'input[name="replace"]' ).show(); + $( 'textarea[name="replace"]' ).hide(); + $( 'input[name="replace"]' ).val( $( 'textarea[name="replace"]' ).val() ); + } + }, + + paste: function() { + + navigator.clipboard.readText().then( text => { + codiad.editor.getActive().insert( text ) + }); + }, + + openSort: function() { + + if( this.getActive() && codiad.active.getSelectedText() != "" ) { + + codiad.modal.load( 400, 'components/editor/dialog.php?action=sort' ); + codiad.modal.hideOverlay(); + } else { + + codiad.message.error( 'No text selected' ); + } + }, + + sort: function( eol ) { + + let text = $( '#modal textarea[name="sort"]' ).val(); + let array = text.split( eol ); + array = array.sort( codiad.editor.sort_a ); + let sorted = array.join( eol ); + + console.log( text, eol, array, sorted ); + codiad.modal.unload(); + codiad.editor.getActive().insert( sorted ); + codiad.editor.getActive().focus(); + }, + + sort_a: function( a, b ) { + + let pos = 0; + let case_sensitive = $( '#modal input[name="case_sensitive"]' ).prop( 'checked' ) + + if( !case_sensitive ) { + + a = a.toLowerCase(); + b = b.toLowerCase(); + } + + if( a < b ) { + + pos = -1; + } else if( a > b ) { + + pos = 1; + } + + return pos; + } + }; + +})( this, jQuery ); diff --git a/components/filemanager/init.js b/components/filemanager/init.js index 651208b..ef867a6 100755 --- a/components/filemanager/init.js +++ b/components/filemanager/init.js @@ -3,29 +3,28 @@ * as-is and without warranty under the MIT License. See * [root]/license.txt for more. This information must remain intact. */ - - (function(global, $){ +( function( global, $ ) { - var codiad = global.codiad; + var codiad = global.codiad; + + $( window ).load( function() { - $(window) - .load(function() { - codiad.filemanager.init(); - }); + codiad.filemanager.init(); + }); - - codiad.filemanager = { + codiad.filemanager = { auto_reload: false, - clipboard: '', - controller: 'components/filemanager/controller.php', - dialog: 'components/filemanager/dialog.php', - dialogUpload: 'components/filemanager/dialog_upload.php', + clipboard: '', + controller: 'components/filemanager/controller.php', + dialog: 'components/filemanager/dialog.php', + dialogUpload: 'components/filemanager/dialog_upload.php', preview: null, + refresh_interval: null, - init: async function() { - - this.noAudio = [ + init: async function() { + + this.noAudio = [ //Audio 'aac', 'aif', @@ -33,15 +32,15 @@ 'mp4', 'wav', 'ogg', - ], - this.noFiles = [ + ]; + this.noFiles = [ //Files 'exe', 'pdf', 'zip', 'tar', 'tar.gz', - ], + ]; this.noImages = [ //Images 'ico', @@ -51,914 +50,992 @@ 'png', 'gif', 'bmp', - ], + ]; - - this.noOpen = this.noAudio.concat( this.noFiles, this.noImages ), - this.noBrowser = this.noAudio.concat( this.noImages ), - - // Initialize node listener - this.nodeListener(); - this.auto_reload = ( await codiad.settings.get_option( "codiad.filemanager.autoReloadPreview" ) == "true" ); - - console.log( this.auto_reload ); - - amplify.subscribe( 'settings.save', async function() { - - let option = ( await codiad.settings.get_option( "codiad.filemanager.autoReloadPreview" ) == "true" ); - if( option != codiad.filemanager.auto_reload ) { - - //codiad.auto_save.reload_interval(); - window.location.reload( true ); - } + + this.noOpen = this.noAudio.concat( this.noFiles, this.noImages ), + this.noBrowser = this.noAudio.concat( this.noImages ), + + // Initialize node listener + this.nodeListener(); + this.auto_reload = ( await codiad.settings.get_option( "codiad.filemanager.autoReloadPreview" ) == "true" ); + + console.log( this.auto_reload ); + + amplify.subscribe( 'settings.save', async function() { + + let option = ( await codiad.settings.get_option( "codiad.filemanager.autoReloadPreview" ) == "true" ); + if( option != codiad.filemanager.auto_reload ) { + + //codiad.auto_save.reload_interval(); + window.location.reload( true ); + } }); - - /* Subscribe to know when a file become active. */ + + /* Subscribe to know when a file become active. */ amplify.subscribe( 'active.onFocus', async function( path ) { let _this = codiad.filemanager; let editor = codiad.editor.getActive(); - + if( _this.auto_reload && editor !== null ) { - - codiad.editor.getActive().addEventListener( "change", _this.refreshPreview ); - } + + codiad.editor.getActive().addEventListener( "change", _this.refreshPreview ); + } }); - - // Load uploader - $.loadScript("components/filemanager/upload_scripts/jquery.ui.widget.js", true); - $.loadScript("components/filemanager/upload_scripts/jquery.iframe-transport.js", true); - $.loadScript("components/filemanager/upload_scripts/jquery.fileupload.js", true); - }, - - ////////////////////////////////////////////////////////////////// - // Listen for dbclick events on nodes - ////////////////////////////////////////////////////////////////// - - nodeListener: function() { - var _this = this; - - $('#file-manager').on('selectstart', false); - - $('#file-manager span') - .live('click', function() { // Open or Expand - if ($(this).parent().children("a").attr('data-type') == 'directory') { - _this.index($(this).parent().children("a") - .attr('data-path')); - } else { - _this.openFile($(this).parent().children("a") - .attr('data-path')); - } - if (!$(this).hasClass('none')) { - if ($(this).hasClass('plus')) { - $(this).removeClass('plus') - $(this).addClass('minus'); - } else { - $(this).removeClass('minus') - $(this).addClass('plus'); - } - } - }); - $('#file-manager a') - .live('dblclick', function() { // Open or Expand - if (!codiad.editor.settings.fileManagerTrigger) { - if ($(this) - .hasClass('directory')) { - _this.index($(this) - .attr('data-path')); - } else { - _this.openFile($(this) - .attr('data-path')); - } - if (!$(this).parent().children("span").hasClass('none')) { - if ($(this).parent().children("span").hasClass('plus')) { - $(this).parent().children("span").removeClass('plus') - $(this).parent().children("span").addClass('minus'); - } else { - $(this).parent().children("span").removeClass('minus') - $(this).parent().children("span").addClass('plus'); - } - } - } - }) - .live('click', function() { // Open or Expand - if (codiad.editor.settings.fileManagerTrigger) { - if ($(this) - .hasClass('directory')) { - _this.index($(this) - .attr('data-path')); - } else { - _this.openFile($(this) - .attr('data-path')); - } - if (!$(this).parent().children("span").hasClass('none')) { - if ($(this).parent().children("span").hasClass('plus')) { - $(this).parent().children("span").removeClass('plus') - $(this).parent().children("span").addClass('minus'); - } else { - $(this).parent().children("span").removeClass('minus') - $(this).parent().children("span").addClass('plus'); - } - } - } - }) - .live("contextmenu", function(e) { // Context Menu - e.preventDefault(); - _this.contextMenuShow(e, $(this) - .attr('data-path'), $(this) - .attr('data-type'), $(this) - .html()); - $(this) - .addClass('context-menu-active'); - }); - }, - - ////////////////////////////////////////////////////////////////// - // Context Menu - ////////////////////////////////////////////////////////////////// - - contextMenuShow: function(e, path, type, name) { - var _this = this; - $('#context-menu a, #context-menu hr').hide(); - // Selective options - switch (type) { - case 'directory': - $('#context-menu .directory-only, #context-menu .non-root, #context-menu .both').show(); - break; - case 'file': - $('#context-menu .file-only, #context-menu .non-root, #context-menu .both').show(); - break; - case 'root': - $('#context-menu .directory-only, #context-menu .root-only').show(); - break; - case 'editor': - $('#context-menu .editor-only').show(); - break; - } - if(codiad.project.isAbsPath($('#file-manager a[data-type="root"]').attr('data-path'))) { - $('#context-menu .no-external').hide(); - } else if( type == "editor" ) { - $('#context-menu .no-external').hide(); - } else { - $('#context-menu .no-external').show(); - } - // Show menu - var top = e.pageY; - if (top > $(window).height() - $('#context-menu').height()) { - top -= $('#context-menu').height(); - } - if (top < 10) { - top = 10; - } - var max = $(window).height() - top - 10; - - $('#context-menu') - .css({ - 'top': top + 'px', - 'left': e.pageX + 'px', - 'max-height': max + 'px' - }) - .fadeIn(200) - .attr('data-path', path) - .attr('data-type', type) - .attr('data-name', name); - // Show faded 'paste' if nothing in clipboard - if (this.clipboard === '') { - $('#context-menu a[content="Paste"]') - .addClass('disabled'); - } else { - $('#context-menu a[data-action="paste"]') - .removeClass('disabled'); - } - // Hide menu - /** - * make sure that the user has moved their mouse far enough - * away from the context menu to warrant a close. - */ - $('#file-manager, #editor-region').on( 'mousemove', codiad.filemanager.contextCheckMouse ); - $('#context-menu, #editor-region').on( 'paste', codiad.editor.paste ); - - /* Notify listeners. */ - amplify.publish('context-menu.onShow', {e: e, path: path, type: type}); - // Hide on click - $('#context-menu a') - .click(function() { - _this.contextMenuHide(); - }); - }, + // Load uploader + $.loadScript( "components/filemanager/upload_scripts/jquery.ui.widget.js", true ); + $.loadScript( "components/filemanager/upload_scripts/jquery.iframe-transport.js", true ); + $.loadScript( "components/filemanager/upload_scripts/jquery.fileupload.js", true ); + }, + + ////////////////////////////////////////////////////////////////// + // Listen for dbclick events on nodes + ////////////////////////////////////////////////////////////////// + + nodeListener: function() { + + let _this = this; + + $( '#file-manager' ).on( 'selectstart', false ); + + $( '#file-manager span' ) + .live( 'click', function() { // Open or Expand + if( $( this ).parent().children( "a" ).attr( 'data-type' ) == 'directory' ) { + _this.index( $( this ).parent().children( "a" ) + .attr( 'data-path' ) ); + } else { + _this.openFile( $( this ).parent().children( "a" ) + .attr( 'data-path' ) ); + } + if( !$( this ).hasClass( 'none' ) ) { + if( $( this ).hasClass( 'plus' ) ) { + $( this ).removeClass( 'plus' ) + $( this ).addClass( 'minus' ); + } else { + $( this ).removeClass( 'minus' ) + $( this ).addClass( 'plus' ); + } + } + }); + $( '#file-manager a' ) + .live( 'dblclick', function() { // Open or Expand + if( !codiad.editor.settings.fileManagerTrigger ) { + if( $( this ) + .hasClass( 'directory' ) ) { + _this.index( $( this ) + .attr( 'data-path' ) ); + } else { + _this.openFile( $( this ) + .attr( 'data-path' ) ); + } + if( !$( this ).parent().children( "span" ).hasClass( 'none' ) ) { + if( $( this ).parent().children( "span" ).hasClass( 'plus' ) ) { + $( this ).parent().children( "span" ).removeClass( 'plus' ) + $( this ).parent().children( "span" ).addClass( 'minus' ); + } else { + $( this ).parent().children( "span" ).removeClass( 'minus' ) + $( this ).parent().children( "span" ).addClass( 'plus' ); + } + } + } + }) + .live( 'click', function() { // Open or Expand + if( codiad.editor.settings.fileManagerTrigger ) { + if( $( this ) + .hasClass( 'directory' ) ) { + _this.index( $( this ) + .attr( 'data-path' ) ); + } else { + _this.openFile( $( this ) + .attr( 'data-path' ) ); + } + if( !$( this ).parent().children( "span" ).hasClass( 'none' ) ) { + if( $( this ).parent().children( "span" ).hasClass( 'plus' ) ) { + $( this ).parent().children( "span" ).removeClass( 'plus' ) + $( this ).parent().children( "span" ).addClass( 'minus' ); + } else { + $( this ).parent().children( "span" ).removeClass( 'minus' ) + $( this ).parent().children( "span" ).addClass( 'plus' ); + } + } + } + }) + .live( "contextmenu", function( e ) { // Context Menu + e.preventDefault(); + _this.contextMenuShow( e, $( this ) + .attr( 'data-path' ), $( this ) + .attr( 'data-type' ), $( this ) + .html() ); + $( this ) + .addClass( 'context-menu-active' ); + }); + }, + + ////////////////////////////////////////////////////////////////// + // Context Menu + ////////////////////////////////////////////////////////////////// + + contextMenuShow: function( e, path, type, name ) { + let _this = this; + + $( '#context-menu a, #context-menu hr' ).hide(); + // Selective options + switch ( type ) { + case 'directory': + $( '#context-menu .directory-only, #context-menu .non-root, #context-menu .both' ).show(); + break; + case 'file': + $( '#context-menu .file-only, #context-menu .non-root, #context-menu .both' ).show(); + break; + case 'root': + $( '#context-menu .directory-only, #context-menu .root-only' ).show(); + break; + case 'editor': + $( '#context-menu .editor-only' ).show(); + break; + } + if( codiad.project.isAbsPath( $( '#file-manager a[data-type="root"]' ).attr( 'data-path' ) ) ) { + $( '#context-menu .no-external' ).hide(); + } else if( type == "editor" ) { + $( '#context-menu .no-external' ).hide(); + } else { + $( '#context-menu .no-external' ).show(); + } + // Show menu + var top = e.pageY; + if( top > $( window ).height() - $( '#context-menu' ).height() ) { + top -= $( '#context-menu' ).height(); + } + if( top < 10 ) { + top = 10; + } + var max = $( window ).height() - top - 10; + + $( '#context-menu' ) + .css( { + 'top': top + 'px', + 'left': e.pageX + 'px', + 'max-height': max + 'px' + }) + .fadeIn( 200 ) + .attr( 'data-path', path ) + .attr( 'data-type', type ) + .attr( 'data-name', name ); + // Show faded 'paste' if nothing in clipboard + if( this.clipboard === '' ) { + $( '#context-menu a[content="Paste"]' ) + .addClass( 'disabled' ); + } else { + $( '#context-menu a[data-action="paste"]' ) + .removeClass( 'disabled' ); + } + // Hide menu + /** + * make sure that the user has moved their mouse far enough + * away from the context menu to warrant a close. + */ + $( '#file-manager, #editor-region' ).on( 'mousemove', codiad.filemanager.contextCheckMouse ); + $( '#context-menu, #editor-region' ).on( 'paste', codiad.editor.paste ); + + /* Notify listeners. */ + amplify.publish( 'context-menu.onShow', { + e: e, + path: path, + type: type + }); + // Hide on click + $( '#context-menu a' ) + .click( function() { + _this.contextMenuHide(); + }); + }, contextCheckMouse: function( e ) { - - let offset = $('#context-menu').offset(); - let bottom = offset.top + $('#context-menu').outerHeight( true ) + 20; - let left = offset.left - 20; - let right = offset.left + $('#context-menu').outerWidth( true ) + 20; - let top = offset.top - 20; - - if( ( e.clientX > right || e.clientX < left ) || ( e.clientY > bottom || e.clientY < top ) ) { - - $('#file-manager, #editor-region').off( 'mousemove', codiad.filemanager.contextCheckMouse ); - $('#context-menu, #editor-region').off( 'paste', codiad.editor.paste ); - codiad.filemanager.contextMenuHide(); - } - }, + + let offset = $( '#context-menu' ).offset(); + let bottom = offset.top + $( '#context-menu' ).outerHeight( true ) + 20; + let left = offset.left - 20; + let right = offset.left + $( '#context-menu' ).outerWidth( true ) + 20; + let top = offset.top - 20; + + if( ( e.clientX > right || e.clientX < left ) || ( e.clientY > bottom || e.clientY < top ) ) { + + $( '#file-manager, #editor-region' ).off( 'mousemove', codiad.filemanager.contextCheckMouse ); + $( '#context-menu, #editor-region' ).off( 'paste', codiad.editor.paste ); + codiad.filemanager.contextMenuHide(); + } + }, - contextMenuHide: function() { - $('#context-menu') - .fadeOut(200); - $('#file-manager a') - .removeClass('context-menu-active'); - /* Notify listeners. */ - amplify.publish('context-menu.onHide'); - }, - - ////////////////////////////////////////////////////////////////// - // Return the node name (sans path) - ////////////////////////////////////////////////////////////////// - - getShortName: function(path) { - return path.split('/') - .pop(); - }, - - ////////////////////////////////////////////////////////////////// - // Return extension - ////////////////////////////////////////////////////////////////// - - getExtension: function(path) { - return path.split('.') - .pop(); - }, - - ////////////////////////////////////////////////////////////////// - // Return type - ////////////////////////////////////////////////////////////////// - - getType: function(path) { - - if( path.match( /\\/g ) ) { - - path = path.replace( '\\', '\\\\' ); - } - - return $('#file-manager a[data-path="' + path + '"]').attr('data-type'); - }, - - ////////////////////////////////////////////////////////////////// - // Create node in file tree - ////////////////////////////////////////////////////////////////// - - createObject: function(parent, path, type) { - // NODE FORMAT:
  • {short_name}
  • - var parentNode = $('#file-manager a[data-path="' + parent + '"]'); - if (!$('#file-manager a[data-path="' + path + '"]') - .length) { // Doesn't already exist - if (parentNode.hasClass('open') && parentNode.hasClass('directory')) { // Only append node if parent is open (and a directory) - var shortName = this.getShortName(path); - if (type == 'directory') { - var appendage = '
  • ' + shortName + '
  • '; - } else { - var appendage = '
  • ' + shortName + '
  • '; - } - if (parentNode.siblings('ul') - .length) { // UL exists, other children to play with - parentNode.siblings('ul') - .append(appendage); - } else { - $('
      ' + appendage + '
    ') - .insertAfter(parentNode); - } - } else { - parentNode.parent().children('span').removeClass('none'); - parentNode.parent().children('span').addClass('plus'); - } - } - }, - - ////////////////////////////////////////////////////////////////// - // Loop out all files and folders in directory path - ////////////////////////////////////////////////////////////////// - - indexFiles: [], - - index: function(path, rescan) { - var _this = this; - if (rescan === undefined) { - rescan = false; - } - node = $('#file-manager a[data-path="' + path + '"]'); - if (node.hasClass('open') && !rescan) { - node.parent('li') - .children('ul') - .slideUp(300, function() { - $(this) - .remove(); - node.removeClass('open'); - }); - } else { - node.addClass('loading'); - $.get(this.controller + '?action=index&path=' + encodeURIComponent(path), function(data) { - node.addClass('open'); - var objectsResponse = codiad.jsend.parse(data); - if (objectsResponse != 'error') { - /* Notify listener */ - _this.indexFiles = objectsResponse.index; - amplify.publish("filemanager.onIndex", {path: path, files: _this.indexFiles}); - var files = _this.indexFiles; - if (files.length > 0) { - if (node.parent().children('span').hasClass('plus')) { - node.parent().children('span').removeClass('plus').addClass('minus'); - } - var display = 'display:none;'; - if (rescan) { - display = ''; - } - var appendage = '
      '; - $.each(files, function(index) { - var ext = ''; - var name = files[index].name.replace(path, ''); - var nodeClass = 'none'; - name = name.split('/') - .join(' '); - if (files[index].type == 'file') { - var ext = ' ext-' + name.split('.') - .pop(); - } - if(files[index].type == 'directory' && files[index].size > 0) { - nodeClass = 'plus'; - } - appendage += '
    • ' + name + '
    • '; - }); - appendage += '
    '; - if (rescan) { - node.parent('li') - .children('ul') - .remove(); - } - $(appendage) - .insertAfter(node); - if (!rescan) { - node.siblings('ul') - .slideDown(300); - } - } - } - node.removeClass('loading'); - if (rescan && _this.rescanChildren.length > _this.rescanCounter) { - _this.rescan(_this.rescanChildren[_this.rescanCounter++]); - } else { - _this.rescanChildren = []; - _this.rescanCounter = 0; - } - }); - } - }, - - rescanChildren: [], - - rescanCounter: 0, - - rescan: function(path) { - var _this = this; - if (this.rescanCounter === 0) { - // Create array of open directories - node = $('#file-manager a[data-path="' + path + '"]'); - node.parent().find('a.open').each(function() { - _this.rescanChildren.push($(this).attr('data-path')); - }); - } - - this.index(path, true); - }, - - ////////////////////////////////////////////////////////////////// - // Open File - ////////////////////////////////////////////////////////////////// - - openFile: function(path, focus) { - - /* Notify listeners. */ - amplify.publish('filemanager.onFileWillOpen', {path: path}); - - if (focus === undefined) { - focus = true; - } - var node = $('#file-manager a[data-path="' + path + '"]'); - var ext = this.getExtension(path); - if ($.inArray(ext.toLowerCase(), this.noOpen) < 0) { - node.addClass('loading'); - $.get(this.controller + '?action=open&path=' + encodeURIComponent(path), function(data) { - var openResponse = codiad.jsend.parse(data); - if (openResponse != 'error') { - node.removeClass('loading'); - codiad.active.open(path, openResponse.content, openResponse.mtime, false, focus); - } - }); - } else { - if(!codiad.project.isAbsPath(path)) { - if ($.inArray(ext.toLowerCase(), this.noBrowser) < 0) { - this.download(path); - } else { - this.openInModal(path); - } - } else { - codiad.message.error(i18n('Unable to open file in Browser while using absolute path.')); - } - } - }, - - ////////////////////////////////////////////////////////////////// - // Open in browser - ////////////////////////////////////////////////////////////////// - - openInBrowser: function(path) { - - let _this = this; - - $.ajax({ - url: this.controller + '?action=open_in_browser&path=' + encodeURIComponent(path), - success: function(data) { - var openIBResponse = codiad.jsend.parse(data); - if (openIBResponse != 'error') { - - _this.preview = window.open( openIBResponse.url, '_newtab' ); - - let editor = codiad.editor.getActive(); - - if( _this.auto_reload && editor !== null ) { - - codiad.editor.getActive().addEventListener( "change", _this.refreshPreview ); - } - - - } - }, - async: false - }); - }, - - refreshPreview: function( event ) { - - _this = codiad.filemanager; - - if( _this.preview == null ) { - - return; - } - - try { - - if( ( typeof _this.preview.location.reload ) == "undefined" ) { - - _this.preview = null; - codiad.editor.getActive().removeEventListener( "change", _this.refreshPreview ); - return; - } - _this.preview.location.reload( true ); - } catch( e ) { - - console.log( e ); - codiad.message.error( 'Please close your previously opened preview window.' ); - _this.preview = null; - codiad.editor.getActive().removeEventListener( "change", _this.refreshPreview ); - } - }, - - openInModal: function(path) { - - let type = ""; - var ext = this.getExtension(path).toLowerCase(); - - if ( this.noAudio.includes(ext) ) { - - type = 'music_preview'; - } else if ( this.noImages.includes(ext) ) { - - type = 'preview'; - } - - codiad.modal.load(250, this.dialog, { - action: type, - path: path - }); - }, - saveModifications: function(path, data, callbacks, save=true){ - callbacks = callbacks || {}; - var _this = this, action, data; - var notifySaveErr = function() { - codiad.message.error(i18n('File could not be saved')); - if (typeof callbacks.error === 'function') { - var context = callbacks.context || _this; - callbacks.error.apply(context, [data]); - } - } - $.post(this.controller + '?action=modify&path=' + encodeURIComponent(path), data, function(resp){ - resp = $.parseJSON(resp); - if (resp.status == 'success') { - if ( save === true ) { - codiad.message.success(i18n('File saved')); - } - if (typeof callbacks.success === 'function'){ - var context = callbacks.context || _this; - callbacks.success.call(context, resp.data.mtime); - } - } else { - if (resp.message == 'Client is out of sync'){ - var reload = confirm( - "Server has a more updated copy of the file. Would "+ - "you like to refresh the contents ? Pressing no will "+ - "cause your changes to override the server's copy upon "+ - "next save." - ); - if (reload) { - codiad.active.close(path); - codiad.active.removeDraft(path); - _this.openFile(path); - } else { - var session = codiad.editor.getActive().getSession(); - session.serverMTime = null; - session.untainted = null; - } - } else codiad.message.error(i18n('File could not be saved')); - if (typeof callbacks.error === 'function') { - var context = callbacks.context || _this; - callbacks.error.apply(context, [resp.data]); - } - } - }).error(notifySaveErr); - }, - ////////////////////////////////////////////////////////////////// - // Save file - ////////////////////////////////////////////////////////////////// + contextMenuHide: function() { + $( '#context-menu' ) + .fadeOut( 200 ); + $( '#file-manager a' ) + .removeClass( 'context-menu-active' ); + /* Notify listeners. */ + amplify.publish( 'context-menu.onHide' ); + }, - saveFile: function(path, content, callbacks, save=true) { - this.saveModifications(path, {content: content}, callbacks, save); - }, + ////////////////////////////////////////////////////////////////// + // Return the node name (sans path) + ////////////////////////////////////////////////////////////////// - savePatch: function(path, patch, mtime, callbacks, alerts) { - if (patch.length > 0) - this.saveModifications(path, {patch: patch, mtime: mtime}, callbacks, alerts); - else if (typeof callbacks.success === 'function'){ - var context = callbacks.context || this; - callbacks.success.call(context, mtime); - } - }, + getShortName: function( path ) { + return path.split( '/' ) + .pop(); + }, - ////////////////////////////////////////////////////////////////// - // Create Object - ////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////// + // Return extension + ////////////////////////////////////////////////////////////////// - createNode: function(path, type) { - codiad.modal.load(250, this.dialog, { - action: 'create', - type: type, - path: path - }); - $('#modal-content form') - .live('submit', function(e) { - let project = codiad.project.getCurrent(); - e.preventDefault(); - var shortName = $('#modal-content form input[name="object_name"]') - .val(); - var path = $('#modal-content form input[name="path"]') - .val(); - var type = $('#modal-content form input[name="type"]') - .val(); - var createPath = path + '/' + shortName; - $.get(codiad.filemanager.controller + '?action=create&path=' + encodeURIComponent(createPath) + '&type=' + type, function(data) { - var createResponse = codiad.jsend.parse(data); - if (createResponse != 'error') { - codiad.message.success(type.charAt(0) - .toUpperCase() + type.slice(1) + ' Created'); - codiad.modal.unload(); - // Add new element to filemanager screen - codiad.filemanager.createObject(path, createPath, type); - if(type == 'file') { - codiad.filemanager.openFile(createPath, true); - } - - codiad.filemanager.rescan( project ); - - /* Notify listeners. */ - amplify.publish('filemanager.onCreate', {createPath: createPath, path: path, shortName: shortName, type: type}); - } - }); - }); - }, - - ////////////////////////////////////////////////////////////////// - // Copy to Clipboard - ////////////////////////////////////////////////////////////////// - - copyNode: function(path) { - this.clipboard = path; - codiad.message.success(i18n('Copied to Clipboard')); - }, - - ////////////////////////////////////////////////////////////////// - // Paste - ////////////////////////////////////////////////////////////////// - - pasteNode: function(path) { - var _this = this; - if (this.clipboard == '') { - codiad.message.error(i18n('Nothing in Your Clipboard')); - } else if (path == this.clipboard) { - codiad.message.error(i18n('Cannot Paste Directory Into Itself')); - } else { - let project = codiad.project.getCurrent(); - var shortName = _this.getShortName(_this.clipboard); - if ($('#file-manager a[data-path="' + path + '/' + shortName + '"]') - .length) { // Confirm overwrite? - codiad.modal.load(400, this.dialog, { - action: 'overwrite', - path: path + '/' + shortName - }); - $('#modal-content form') - .live('submit', function(e) { - e.preventDefault(); - var duplicate = false; - if($('#modal-content form select[name="or_action"]').val()==1){ - duplicate=true; console.log('Dup!'); - } - _this.processPasteNode(path,duplicate); - }); - } else { // No conflicts; proceed... - _this.processPasteNode(path,false); - } - - codiad.filemanager.rescan( project ); - } - }, + getExtension: function( path ) { + return path.split( '.' ) + .pop(); + }, - processPasteNode: function(path,duplicate) { - var _this = this; - var shortName = this.getShortName(this.clipboard); - var type = this.getType(this.clipboard); - - $.get(this.controller + '?action=duplicate&path=' + - encodeURIComponent(this.clipboard) + '&destination=' + - encodeURIComponent(path + '/' + shortName) + '&duplicate=' + encodeURIComponent( duplicate ), function(data) { - var pasteResponse = codiad.jsend.parse(data); - if (pasteResponse != 'error') { - _this.createObject(path, path + '/' + shortName, type); - codiad.modal.unload(); - /* Notify listeners. */ - amplify.publish('filemanager.onPaste', {path: path, shortName: shortName, duplicate: duplicate}); - } - }); - }, + ////////////////////////////////////////////////////////////////// + // Return type + ////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////// - // Rename - ////////////////////////////////////////////////////////////////// + getType: function( path ) { + + if( path.match( /\\/g ) ) { + + path = path.replace( '\\', '\\\\' ); + } + + return $( '#file-manager a[data-path="' + path + '"]' ).attr( 'data-type' ); + }, - renameNode: function(path) { - var shortName = this.getShortName(path); - var type = this.getType(path); - var _this = this; - codiad.modal.load(250, this.dialog, { action: 'rename', path: path, short_name: shortName, type: type}); - $('#modal-content form') - .live('submit', function(e) { - let project = codiad.project.getCurrent(); - e.preventDefault(); - var newName = $('#modal-content form input[name="object_name"]') - .val(); - // Build new path - var arr = path.split('/'); - var temp = new Array(); - for (i = 0; i < arr.length - 1; i++) { - temp.push(arr[i]) - } - var newPath = temp.join('/') + '/' + newName; - $.get(_this.controller, { action: 'modify', path: path, new_name: newName} , function(data) { - var renameResponse = codiad.jsend.parse(data); - let renamedMessage = ""; - if (renameResponse != 'error') { - - if( type == undefined ) { - - renamedMessage = 'Successfully Renamed' - } else { - - renamedMessage = type.charAt(0).toUpperCase() + type.slice(1) + ' Renamed' - } - - codiad.message.success(renamedMessage); - var node = $('#file-manager a[data-path="' + path + '"]'); - // Change pathing and name for node - node.attr('data-path', newPath) - .html(newName); - if (type == 'file') { // Change icons for file - curExtClass = 'ext-' + _this.getExtension(path); - newExtClass = 'ext-' + _this.getExtension(newPath); - $('#file-manager a[data-path="' + newPath + '"]') - .removeClass(curExtClass) - .addClass(newExtClass); - } else { // Change pathing on any sub-files/directories - _this.repathSubs(path, newPath); - } - // Change any active files - codiad.active.rename(path, newPath); - codiad.modal.unload(); - codiad.filemanager.rescan( project ); - /* Notify listeners. */ - amplify.publish('filemanager.onRename', {path: path, newPath: newPath, project: project }); - } - }); - }); - }, - - repathSubs: function(oldPath, newPath) { - $('#file-manager a[data-path="' + newPath + '"]') - .siblings('ul') - .find('a') - .each(function() { - // Hit the children, hit 'em hard - var curPath = $(this) - .attr('data-path'); - var revisedPath = curPath.replace(oldPath, newPath); - $(this) - .attr('data-path', revisedPath); - }); - }, - - ////////////////////////////////////////////////////////////////// - // Delete - ////////////////////////////////////////////////////////////////// - - deleteNode: function(path) { - var _this = this; - codiad.modal.load(400, this.dialog, { - action: 'delete', - path: path - }); - $('#modal-content form') - .live('submit', function(e) { - e.preventDefault(); - $.get(_this.controller + '?action=delete&path=' + encodeURIComponent(path), function(data) { - var deleteResponse = codiad.jsend.parse(data); - if (deleteResponse != 'error') { - var node = $('#file-manager a[data-path="' + path + '"]'); - let parent_path = node.parent().parent().prev().attr('data-path'); - node.parent('li').remove(); - // Close any active files - $('#active-files a') - .each(function() { - var curPath = $(this) - .attr('data-path'); - if (curPath.indexOf(path) == 0) { - codiad.active.remove(curPath); - } - }); - /* Notify listeners. */ - amplify.publish('filemanager.onDelete', {deletePath: path, path: parent_path }); - } - codiad.modal.unload(); - }); - }); - }, + ////////////////////////////////////////////////////////////////// + // Create node in file tree + ////////////////////////////////////////////////////////////////// - deleteInnerNode: function(path) { - var _this = this; - codiad.modal.load(400, this.dialog, { - action: 'delete', - path: path - }); - $('#modal-content form') - .live('submit', function(e) { - e.preventDefault(); - $.get(_this.controller + '?action=deleteInner&path=' + encodeURIComponent(path), function(data) { - var deleteResponse = codiad.jsend.parse(data); - if (deleteResponse != 'error') { - var node = $('#file-manager a[data-path="' + path + '"]').parent('ul').remove(); + createObject: function( parent, path, type ) { + // NODE FORMAT:
  • {short_name}
  • + var parentNode = $( '#file-manager a[data-path="' + parent + '"]' ); + if( !$( '#file-manager a[data-path="' + path + '"]' ) + .length ) { // Doesn't already exist + if( parentNode.hasClass( 'open' ) && parentNode.hasClass( 'directory' ) ) { // Only append node if parent is open (and a directory) + var shortName = this.getShortName( path ); + if( type == 'directory' ) { + var appendage = '
  • ' + shortName + '
  • '; + } else { + var appendage = '
  • ' + shortName + '
  • '; + } + if( parentNode.siblings( 'ul' ) + .length ) { // UL exists, other children to play with + parentNode.siblings( 'ul' ) + .append( appendage ); + } else { + $( '
      ' + appendage + '
    ' ) + .insertAfter( parentNode ); + } + } else { + parentNode.parent().children( 'span' ).removeClass( 'none' ); + parentNode.parent().children( 'span' ).addClass( 'plus' ); + } + } + }, + + ////////////////////////////////////////////////////////////////// + // Loop out all files and folders in directory path + ////////////////////////////////////////////////////////////////// + + indexFiles: [], + + index: function( path, rescan ) { + let _this = this; + if( rescan === undefined ) { + rescan = false; + } + node = $( '#file-manager a[data-path="' + path + '"]' ); + if( node.hasClass( 'open' ) && !rescan ) { + node.parent( 'li' ) + .children( 'ul' ) + .slideUp( 300, function() { + $( this ) + .remove(); + node.removeClass( 'open' ); + }); + } else { + + node.addClass( 'loading' ); + $.get( this.controller + '?action=index&path=' + encodeURIComponent( path ), function( data ) { + + node.addClass( 'open' ); + var objectsResponse = codiad.jsend.parse( data ); + if( objectsResponse != 'error' ) { - // Close any active files - $('#active-files a') - .each(function() { - var curPath = $(this) - .attr('data-path'); - if (curPath.indexOf(path) == 0) { - codiad.active.remove(curPath); - } - }); - - //Rescan Folder - node.parent() - .find('a.open') - .each(function() { - _this.rescanChildren.push($(this) - .attr('data-path')); - }); - - /* Notify listeners. */ - amplify.publish('filemanager.onDelete', {deletePath: path + "/*", path: path }); - } - codiad.modal.unload(); - }); - }); - }, - ////////////////////////////////////////////////////////////////// - // Search - ////////////////////////////////////////////////////////////////// - - search: function(path) { - codiad.modal.load(500, this.dialog,{ - action: 'search', - path: path - }); - codiad.modal.load_process.done( async function() { - var lastSearched = JSON.parse( await codiad.settings.get_option("lastSearched")); - if(lastSearched) { - $('#modal-content form input[name="search_string"]').val(lastSearched.searchText); - $('#modal-content form input[name="search_file_type"]').val(lastSearched.fileExtension); - $('#modal-content form select[name="search_type"]').val(lastSearched.searchType); - if(lastSearched.searchResults != '') { - $('#filemanager-search-results').slideDown().html(lastSearched.searchResults); - } - } - }); - codiad.modal.hideOverlay(); - var _this = this; - $('#modal-content form') - .live('submit', function(e) { - $('#filemanager-search-processing') - .show(); - e.preventDefault(); - searchString = $('#modal-content form input[name="search_string"]') - .val(); - fileExtensions=$('#modal-content form input[name="search_file_type"]') - .val(); - searchFileType=$.trim(fileExtensions); - if (searchFileType != '') { - //season the string to use in find command - searchFileType = "\\(" + searchFileType.replace(/\s+/g, "\\|") + "\\)"; - } - searchType = $('#modal-content form select[name="search_type"]') - .val(); - $.post(_this.controller + '?action=search&path=' + encodeURIComponent(path) + '&type=' + searchType, { - search_string: searchString, - search_file_type: searchFileType - }, function(data) { - searchResponse = codiad.jsend.parse(data); - var results = ''; - if (searchResponse != 'error') { - $.each(searchResponse.index, function(key, val) { - // Cleanup file format - if(val['file'].substr(-1) == '/') { - val['file'] = val['file'].substr(0, str.length - 1); - } - val['file'] = val['file'].replace('//','/'); - // Add result - results += ''; - }); - $('#filemanager-search-results') - .slideDown() - .html(results); - } else { - $('#filemanager-search-results') - .slideUp(); - } - _this.saveSearchResults(searchString, searchType, fileExtensions, results); - $('#filemanager-search-processing') - .hide(); - }); - }); - }, - - ///////////////////////////////////////////////////////////////// - // saveSearchResults - ///////////////////////////////////////////////////////////////// - saveSearchResults: function(searchText, searchType, fileExtensions, searchResults) { - var lastSearched = { - searchText: searchText, - searchType: searchType, - fileExtension: fileExtensions, - searchResults: searchResults - }; - localStorage.setItem("lastSearched", JSON.stringify(lastSearched)); - }, - ////////////////////////////////////////////////////////////////// - // Upload - ////////////////////////////////////////////////////////////////// - - uploadToNode: function(path) { - codiad.modal.load(500, this.dialogUpload, {path: path}); - }, - - ////////////////////////////////////////////////////////////////// - // Download - ////////////////////////////////////////////////////////////////// - - download: function(path) { - var type = this.getType(path); - $('#download') - .attr('src', 'components/filemanager/download.php?path=' + encodeURIComponent(path) + '&type=' + type); - } - }; - -})(this, jQuery); + /* Notify listener */ + _this.indexFiles = objectsResponse.index; + amplify.publish( "filemanager.onIndex", { + path: path, + files: _this.indexFiles + }); + var files = _this.indexFiles; + if( files.length > 0 ) { + + if( node.parent().children( 'span' ).hasClass( 'plus' ) ) { + + node.parent().children( 'span' ).removeClass( 'plus' ).addClass( 'minus' ); + } + var display = 'display:none;'; + if( rescan ) { + + display = ''; + } + var appendage = '
      '; + $.each( files, function( index ) { + + var ext = ''; + var name = files[index].name.replace( path, '' ); + var nodeClass = 'none'; + name = name.split( '/' ) + .join( ' ' ); + if( files[index].type == 'file' ) { + + var ext = ' ext-' + name.split( '.' ) + .pop(); + } + if( files[index].type == 'directory' && files[index].size > 0 ) { + + nodeClass = 'plus'; + } + appendage += '
    • ' + name + '
    • '; + }); + appendage += '
    '; + if( rescan ) { + + node.parent( 'li' ) + .children( 'ul' ) + .remove(); + } + $( appendage ) + .insertAfter( node ); + if( !rescan ) { + + node.siblings( 'ul' ) + .slideDown( 300 ); + } + } + } + node.removeClass( 'loading' ); + if( rescan && _this.rescanChildren.length > _this.rescanCounter ) { + + _this.rescan( _this.rescanChildren[_this.rescanCounter++] ); + } else { + + _this.rescanChildren = []; + _this.rescanCounter = 0; + } + }); + } + }, + + rescanChildren: [], + + rescanCounter: 0, + + rescan: function( path ) { + let _this = this; + if( this.rescanCounter === 0 ) { + // Create array of open directories + node = $( '#file-manager a[data-path="' + path + '"]' ); + node.parent().find( 'a.open' ).each( function() { + _this.rescanChildren.push( $( this ).attr( 'data-path' ) ); + }); + } + + this.index( path, true ); + }, + + ////////////////////////////////////////////////////////////////// + // Open File + ////////////////////////////////////////////////////////////////// + + openFile: function( path, focus ) { + + /* Notify listeners. */ + amplify.publish( 'filemanager.onFileWillOpen', { + path: path + }); + + if( focus === undefined ) { + focus = true; + } + var node = $( '#file-manager a[data-path="' + path + '"]' ); + var ext = this.getExtension( path ); + if( $.inArray( ext.toLowerCase(), this.noOpen ) < 0 ) { + node.addClass( 'loading' ); + $.get( this.controller + '?action=open&path=' + encodeURIComponent( path ), function( data ) { + var openResponse = codiad.jsend.parse( data ); + if( openResponse != 'error' ) { + node.removeClass( 'loading' ); + codiad.active.open( path, openResponse.content, openResponse.mtime, false, focus ); + } + }); + } else { + if( !codiad.project.isAbsPath( path ) ) { + if( $.inArray( ext.toLowerCase(), this.noBrowser ) < 0 ) { + this.download( path ); + } else { + this.openInModal( path ); + } + } else { + codiad.message.error( i18n( 'Unable to open file in Browser while using absolute path.' ) ); + } + } + }, + + ////////////////////////////////////////////////////////////////// + // Open in browser + ////////////////////////////////////////////////////////////////// + + openInBrowser: function( path ) { + + let _this = this; + + $.ajax( { + url: this.controller + '?action=open_in_browser&path=' + encodeURIComponent( path ), + success: function( data ) { + var openIBResponse = codiad.jsend.parse( data ); + if( openIBResponse != 'error' ) { + + _this.preview = window.open( openIBResponse.url, '_newtab' ); + + let editor = codiad.editor.getActive(); + + if( _this.auto_reload && editor !== null ) { + + codiad.editor.getActive().addEventListener( "change", _this.refreshPreview ); + } + + + } + }, + async: false + }); + }, + + refreshPreview: function( event ) { + + let _this = codiad.filemanager; + + /** + * When reloading after every change, we encounter performance issues + * in the editor. Therefore, we implement the same logic as the + * auto_save module where we only reload after the user has finished + * changing their document. + */ + + if( _this.refresh_interval !== null ) { + + clearTimeout( _this.refresh_interval ); + _this.refresh_interval = null; + } + _this.refresh_interval = setTimeout( function() { + + if( _this.preview == null ) { + + return; + } + + try { + + if( ( typeof _this.preview.location.reload ) == "undefined" ) { + + _this.preview = null; + codiad.editor.getActive().removeEventListener( "change", _this.refreshPreview ); + return; + } + _this.preview.location.reload( true ); + } catch ( e ) { + + console.log( e ); + codiad.message.error( 'Please close your previously opened preview window.' ); + _this.preview = null; + codiad.editor.getActive().removeEventListener( "change", _this.refreshPreview ); + } + }, 500 ); + }, + + openInModal: function( path ) { + + let type = ""; + var ext = this.getExtension( path ).toLowerCase(); + + if( this.noAudio.includes( ext ) ) { + + type = 'music_preview'; + } else if( this.noImages.includes( ext ) ) { + + type = 'preview'; + } + + codiad.modal.load( 250, this.dialog, { + action: type, + path: path + }); + }, + saveModifications: function( path, data, callbacks, save = true ) { + + callbacks = callbacks || {}; + let _this = this, action; + var notifySaveErr = function() { + codiad.message.error( i18n( 'File could not be saved' ) ); + if( typeof callbacks.error === 'function' ) { + var context = callbacks.context || _this; + callbacks.error.apply( context, [data] ); + } + } + $.post( this.controller + '?action=modify&path=' + encodeURIComponent( path ), data, function( resp ) { + resp = $.parseJSON( resp ); + if( resp.status == 'success' ) { + if( save === true ) { + codiad.message.success( i18n( 'File saved' ) ); + } + if( typeof callbacks.success === 'function' ) { + var context = callbacks.context || _this; + callbacks.success.call( context, resp.data.mtime ); + } + } else { + if( resp.message == 'Client is out of sync' ) { + var reload = confirm( + "Server has a more updated copy of the file. Would " + + "you like to refresh the contents ? Pressing no will " + + "cause your changes to override the server's copy upon " + + "next save." + ); + if( reload ) { + codiad.active.close( path ); + codiad.active.removeDraft( path ); + _this.openFile( path ); + } else { + var session = codiad.editor.getActive().getSession(); + session.serverMTime = null; + session.untainted = null; + } + } else codiad.message.error( i18n( 'File could not be saved' ) ); + if( typeof callbacks.error === 'function' ) { + var context = callbacks.context || _this; + callbacks.error.apply( context, [resp.data] ); + } + } + }).error( notifySaveErr ); + }, + ////////////////////////////////////////////////////////////////// + // Save file + ////////////////////////////////////////////////////////////////// + + saveFile: function( path, content, callbacks, save = true ) { + this.saveModifications( path, { + content: content + }, callbacks, save ); + }, + + savePatch: function( path, patch, mtime, callbacks, alerts ) { + if( patch.length > 0 ) + this.saveModifications( path, { + patch: patch, + mtime: mtime + }, callbacks, alerts ); + else if( typeof callbacks.success === 'function' ) { + var context = callbacks.context || this; + callbacks.success.call( context, mtime ); + } + }, + + ////////////////////////////////////////////////////////////////// + // Create Object + ////////////////////////////////////////////////////////////////// + + createNode: function( path, type ) { + codiad.modal.load( 250, this.dialog, { + action: 'create', + type: type, + path: path + }); + $( '#modal-content form' ) + .live( 'submit', function( e ) { + e.preventDefault(); + var shortName = $( '#modal-content form input[name="object_name"]' ) + .val(); + var path = $( '#modal-content form input[name="path"]' ) + .val(); + var type = $( '#modal-content form input[name="type"]' ) + .val(); + var createPath = path + '/' + shortName; + $.get( codiad.filemanager.controller + '?action=create&path=' + encodeURIComponent( createPath ) + '&type=' + type, function( data ) { + var createResponse = codiad.jsend.parse( data ); + if( createResponse != 'error' ) { + codiad.message.success( type.charAt( 0 ) + .toUpperCase() + type.slice( 1 ) + ' Created' ); + codiad.modal.unload(); + // Add new element to filemanager screen + codiad.filemanager.createObject( path, createPath, type ); + if( type == 'file' ) { + codiad.filemanager.openFile( createPath, true ); + } + + codiad.filemanager.rescan( path ); + + /* Notify listeners. */ + amplify.publish( 'filemanager.onCreate', { + createPath: createPath, + path: path, + shortName: shortName, + type: type + }); + } + }); + }); + }, + + ////////////////////////////////////////////////////////////////// + // Copy to Clipboard + ////////////////////////////////////////////////////////////////// + + copyNode: function( path ) { + this.clipboard = path; + codiad.message.success( i18n( 'Copied to Clipboard' ) ); + }, + + ////////////////////////////////////////////////////////////////// + // Paste + ////////////////////////////////////////////////////////////////// + + pasteNode: function( path ) { + let _this = this; + if( this.clipboard == '' ) { + codiad.message.error( i18n( 'Nothing in Your Clipboard' ) ); + } else if( path == this.clipboard ) { + codiad.message.error( i18n( 'Cannot Paste Directory Into Itself' ) ); + } else { + let project = codiad.project.getCurrent(); + var shortName = _this.getShortName( _this.clipboard ); + if( $( '#file-manager a[data-path="' + path + '/' + shortName + '"]' ) + .length ) { // Confirm overwrite? + codiad.modal.load( 400, this.dialog, { + action: 'overwrite', + path: path + '/' + shortName + }); + $( '#modal-content form' ) + .live( 'submit', function( e ) { + e.preventDefault(); + var duplicate = false; + if( $( '#modal-content form select[name="or_action"]' ).val() == 1 ) { + duplicate = true; + console.log( 'Dup!' ); + } + _this.processPasteNode( path, duplicate ); + }); + } else { // No conflicts; proceed... + _this.processPasteNode( path, false ); + } + + codiad.filemanager.rescan( project ); + } + }, + + processPasteNode: function( path, duplicate ) { + let _this = this; + var shortName = this.getShortName( this.clipboard ); + var type = this.getType( this.clipboard ); + + $.get( this.controller + '?action=duplicate&path=' + + encodeURIComponent( this.clipboard ) + '&destination=' + + encodeURIComponent( path + '/' + shortName ) + '&duplicate=' + encodeURIComponent( duplicate ), + function( data ) { + var pasteResponse = codiad.jsend.parse( data ); + if( pasteResponse != 'error' ) { + _this.createObject( path, path + '/' + shortName, type ); + codiad.modal.unload(); + /* Notify listeners. */ + amplify.publish( 'filemanager.onPaste', { + path: path, + shortName: shortName, + duplicate: duplicate + }); + } + }); + }, + + ////////////////////////////////////////////////////////////////// + // Rename + ////////////////////////////////////////////////////////////////// + + renameNode: function( path ) { + var shortName = this.getShortName( path ); + var type = this.getType( path ); + let _this = this; + codiad.modal.load( 250, this.dialog, { + action: 'rename', + path: path, + short_name: shortName, + type: type + }); + $( '#modal-content form' ) + .live( 'submit', function( e ) { + let project = codiad.project.getCurrent(); + e.preventDefault(); + var newName = $( '#modal-content form input[name="object_name"]' ) + .val(); + // Build new path + var arr = path.split( '/' ); + var temp = new Array(); + for( i = 0; i < arr.length - 1; i++ ) { + temp.push( arr[i] ) + } + var newPath = temp.join( '/' ) + '/' + newName; + $.get( _this.controller, { + action: 'modify', + path: path, + new_name: newName + }, function( data ) { + var renameResponse = codiad.jsend.parse( data ); + let renamedMessage = ""; + if( renameResponse != 'error' ) { + + if( type == undefined ) { + + renamedMessage = 'Successfully Renamed' + } else { + + renamedMessage = type.charAt( 0 ).toUpperCase() + type.slice( 1 ) + ' Renamed' + } + + codiad.message.success( renamedMessage ); + var node = $( '#file-manager a[data-path="' + path + '"]' ); + // Change pathing and name for node + node.attr( 'data-path', newPath ) + .html( newName ); + if( type == 'file' ) { // Change icons for file + curExtClass = 'ext-' + _this.getExtension( path ); + newExtClass = 'ext-' + _this.getExtension( newPath ); + $( '#file-manager a[data-path="' + newPath + '"]' ) + .removeClass( curExtClass ) + .addClass( newExtClass ); + } else { // Change pathing on any sub-files/directories + _this.repathSubs( path, newPath ); + } + // Change any active files + codiad.active.rename( path, newPath ); + codiad.modal.unload(); + + let parent = path.split( '/' ); + parent.pop(); + codiad.filemanager.rescan( parent.join( '/' ) ); + /* Notify listeners. */ + amplify.publish( 'filemanager.onRename', { + path: path, + newPath: newPath, + project: project + }); + } + }); + }); + }, + + repathSubs: function( oldPath, newPath ) { + $( '#file-manager a[data-path="' + newPath + '"]' ) + .siblings( 'ul' ) + .find( 'a' ) + .each( function() { + // Hit the children, hit 'em hard + var curPath = $( this ) + .attr( 'data-path' ); + var revisedPath = curPath.replace( oldPath, newPath ); + $( this ) + .attr( 'data-path', revisedPath ); + }); + }, + + ////////////////////////////////////////////////////////////////// + // Delete + ////////////////////////////////////////////////////////////////// + + deleteNode: function( path ) { + let _this = this; + codiad.modal.load( 400, this.dialog, { + action: 'delete', + path: path + }); + $( '#modal-content form' ) + .live( 'submit', function( e ) { + e.preventDefault(); + $.get( _this.controller + '?action=delete&path=' + encodeURIComponent( path ), function( data ) { + var deleteResponse = codiad.jsend.parse( data ); + if( deleteResponse != 'error' ) { + var node = $( '#file-manager a[data-path="' + path + '"]' ); + let parent_path = node.parent().parent().prev().attr( 'data-path' ); + node.parent( 'li' ).remove(); + // Close any active files + $( '#active-files a' ) + .each( function() { + var curPath = $( this ) + .attr( 'data-path' ); + if( curPath.indexOf( path ) == 0 ) { + codiad.active.remove( curPath ); + } + }); + /* Notify listeners. */ + amplify.publish( 'filemanager.onDelete', { + deletePath: path, + path: parent_path + }); + } + codiad.modal.unload(); + }); + }); + }, + + deleteInnerNode: function( path ) { + let _this = this; + codiad.modal.load( 400, this.dialog, { + action: 'delete', + path: path + }); + $( '#modal-content form' ) + .live( 'submit', function( e ) { + e.preventDefault(); + $.get( _this.controller + '?action=deleteInner&path=' + encodeURIComponent( path ), function( data ) { + var deleteResponse = codiad.jsend.parse( data ); + if( deleteResponse != 'error' ) { + var node = $( '#file-manager a[data-path="' + path + '"]' ).parent( 'ul' ).remove(); + + // Close any active files + $( '#active-files a' ) + .each( function() { + var curPath = $( this ) + .attr( 'data-path' ); + if( curPath.indexOf( path ) == 0 ) { + codiad.active.remove( curPath ); + } + }); + + //Rescan Folder + node.parent() + .find( 'a.open' ) + .each( function() { + _this.rescanChildren.push( $( this ) + .attr( 'data-path' ) ); + }); + + /* Notify listeners. */ + amplify.publish( 'filemanager.onDelete', { + deletePath: path + "/*", + path: path + }); + } + codiad.modal.unload(); + }); + }); + }, + ////////////////////////////////////////////////////////////////// + // Search + ////////////////////////////////////////////////////////////////// + + search: function( path ) { + codiad.modal.load( 500, this.dialog, { + action: 'search', + path: path + }); + codiad.modal.load_process.done( async function() { + var lastSearched = JSON.parse( await codiad.settings.get_option( "lastSearched" ) ); + if( lastSearched ) { + $( '#modal-content form input[name="search_string"]' ).val( lastSearched.searchText ); + $( '#modal-content form input[name="search_file_type"]' ).val( lastSearched.fileExtension ); + $( '#modal-content form select[name="search_type"]' ).val( lastSearched.searchType ); + if( lastSearched.searchResults != '' ) { + $( '#filemanager-search-results' ).slideDown().html( lastSearched.searchResults ); + } + } + }); + codiad.modal.hideOverlay(); + let _this = this; + $( '#modal-content form' ) + .live( 'submit', function( e ) { + $( '#filemanager-search-processing' ) + .show(); + e.preventDefault(); + searchString = $( '#modal-content form input[name="search_string"]' ) + .val(); + fileExtensions = $( '#modal-content form input[name="search_file_type"]' ) + .val(); + searchFileType = $.trim( fileExtensions ); + if( searchFileType != '' ) { + //season the string to use in find command + searchFileType = "\\(" + searchFileType.replace( /\s+/g, "\\|" ) + "\\)"; + } + searchType = $( '#modal-content form select[name="search_type"]' ) + .val(); + $.post( _this.controller + '?action=search&path=' + encodeURIComponent( path ) + '&type=' + searchType, { + search_string: searchString, + search_file_type: searchFileType + }, function( data ) { + searchResponse = codiad.jsend.parse( data ); + var results = ''; + if( searchResponse != 'error' ) { + $.each( searchResponse.index, function( key, val ) { + // Cleanup file format + if( val['file'].substr( -1 ) == '/' ) { + val['file'] = val['file'].substr( 0, str.length - 1 ); + } + val['file'] = val['file'].replace( '//', '/' ); + // Add result + results += ''; + }); + $( '#filemanager-search-results' ) + .slideDown() + .html( results ); + } else { + $( '#filemanager-search-results' ) + .slideUp(); + } + _this.saveSearchResults( searchString, searchType, fileExtensions, results ); + $( '#filemanager-search-processing' ) + .hide(); + }); + }); + }, + + ///////////////////////////////////////////////////////////////// + // saveSearchResults + ///////////////////////////////////////////////////////////////// + saveSearchResults: function( searchText, searchType, fileExtensions, searchResults ) { + var lastSearched = { + searchText: searchText, + searchType: searchType, + fileExtension: fileExtensions, + searchResults: searchResults + }; + localStorage.setItem( "lastSearched", JSON.stringify( lastSearched ) ); + }, + ////////////////////////////////////////////////////////////////// + // Upload + ////////////////////////////////////////////////////////////////// + + uploadToNode: function( path ) { + codiad.modal.load( 500, this.dialogUpload, { + path: path + }); + }, + + ////////////////////////////////////////////////////////////////// + // Download + ////////////////////////////////////////////////////////////////// + + download: function( path ) { + var type = this.getType( path ); + $( '#download' ) + .attr( 'src', 'components/filemanager/download.php?path=' + encodeURIComponent( path ) + '&type=' + type ); + } + }; + +})( this, jQuery ); \ No newline at end of file diff --git a/components/install/install.php b/components/install/install.php index 3185df7..4d9e1ee 100644 --- a/components/install/install.php +++ b/components/install/install.php @@ -1,9 +1,5 @@ "required", "MySQL"=>"", "PGSQL"=>"", - "SQLite3"=>"" + //"SQLite3"=>"" ] as $dep=>$status) { if (extension_loaded(strtolower($dep))) { ?>
    diff --git a/components/project/dialog.php b/components/project/dialog.php index 1e960f1..c71e75f 100755 --- a/components/project/dialog.php +++ b/components/project/dialog.php @@ -107,6 +107,11 @@ switch( $_GET['action'] ) { ?> + + diff --git a/components/settings/settings.editor.php b/components/settings/settings.editor.php index 60bc788..ecea254 100755 --- a/components/settings/settings.editor.php +++ b/components/settings/settings.editor.php @@ -1,201 +1,140 @@ - +
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    - - - -
    - - - -
    - - - -
    - - - -
    - - - -
    - - - -
    - - - -
    - - - -
    - - - -
    - - - -
    - - - -
    + +
    + +
    + +
    + +
    + +
    + +
    + +
    + +
    + +
    + +
    + +
    diff --git a/components/sql/class.sql.php b/components/sql/class.sql.php index ecb9755..f2b6579 100755 --- a/components/sql/class.sql.php +++ b/components/sql/class.sql.php @@ -7,8 +7,8 @@ class sql { const DB_TYPES = array( "MySQL" => "mysql", - "PostgresSQL" => "pgsql", - "SQLite" => "sqlite", + "PostgreSQL" => "pgsql", + //"SQLite" => "sqlite", ); public $connection = null; diff --git a/components/update/class.update.php b/components/update/class.update.php index bdd8b9c..8b2fdb4 100755 --- a/components/update/class.update.php +++ b/components/update/class.update.php @@ -12,7 +12,7 @@ class Update { // CONSTANTS ////////////////////////////////////////////////////////////////// - CONST VERSION = "v.2.9.4.1"; + CONST VERSION = "v.2.9.5"; ////////////////////////////////////////////////////////////////// // PROPERTIES diff --git a/components/update/update.php b/components/update/update.php index 4107bc7..dc2d681 100755 --- a/components/update/update.php +++ b/components/update/update.php @@ -5,11 +5,14 @@ //error_reporting(E_ALL); require_once('../../common.php'); +require_once('../settings/class.settings.php'); require_once('./class.update.php'); -$user_settings_file = DATA . "/settings.php"; -$projects_file = DATA . "/projects.php"; -$users_file = DATA . "/users.php"; + + +$user_settings_file = BASE_PATH . "/data/settings.php"; +$projects_file = BASE_PATH . "/data/projects.php"; +$users_file = BASE_PATH . "/data/users.php"; //checkSession(); if ( ! checkAccess() ) { echo "Error, you do not have access to update Codiad."; @@ -102,8 +105,6 @@ class updater { mkdir( $backup, 00755 ); } - - function copy_backup( $source, $dest ) { // Check for symlinks @@ -173,8 +174,6 @@ class updater { function check_sql() { - require_once('../../common.php'); - require_once('../sql/class.sql.php'); $sql = new sql(); $connection = $sql->connect(); $result = $sql->create_default_tables(); @@ -211,9 +210,6 @@ class updater { function convert() { - require_once('../sql/class.sql.php'); - require_once('../settings/class.settings.php'); - $user_settings_file = DATA . "/settings.php"; $projects_file = DATA . "/projects.php"; $users_file = DATA . "/users.php"; @@ -235,18 +231,22 @@ class updater { if( file_exists( $projects_file ) ) { $projects = getJSON( 'projects.php' ); - foreach( $projects as $project => $data ) { + + if( is_array( $projects ) ) { - $owner = 'nobody'; - $query = "INSERT INTO projects( name, path, owner ) VALUES ( ?, ?, ? );"; - $bind_variables = array( $data["name"], $data["path"], $owner ); - $return = $sql->query( $query, $bind_variables, 0, "rowCount" ); - - if( $return > 0 ) { - } else { + foreach( $projects as $project => $data ) { - $this->restore(); - exit(formatJSEND( "error", "There was an error adding projects to database." )); + $owner = 'nobody'; + $query = "INSERT INTO projects( name, path, owner ) VALUES ( ?, ?, ? );"; + $bind_variables = array( $data["name"], $data["path"], $owner ); + $return = $sql->query( $query, $bind_variables, 0, "rowCount" ); + + if( $return > 0 ) { + } else { + + $this->restore(); + exit( formatJSEND( "error", "There was an error adding projects to database." ) ); + } } } unlink( $projects_file ); @@ -255,28 +255,32 @@ class updater { if( file_exists( $users_file ) ) { $users = getJSON( 'users.php' ); - foreach( $users as $user ) { + + if( is_array( $users ) ) { - if( $user["username"] === $_SESSION["user"] ) { + foreach( $users as $user ) { - $access = "admin"; - } else { + if( $user["username"] === $_SESSION["user"] ) { + + $access = "admin"; + } else { + + $access = "user"; + } + $query = "INSERT INTO users( username, password, access, project ) VALUES ( ?, ?, ?, ? );"; + $bind_variables = array( $user["username"], $user["password"], $access, null ); + $return = $sql->query( $query, $bind_variables, 0, "rowCount" ); - $access = "user"; - } - $query = "INSERT INTO users( username, password, access, project ) VALUES ( ?, ?, ?, ? );"; - $bind_variables = array( $user["username"], $user["password"], $access, null ); - $return = $sql->query( $query, $bind_variables, 0, "rowCount" ); - - if( $return > 0 ) { - - $this->username = $user["username"]; - $this->set_default_options(); - //echo formatJSEND( "success", array( "username" => $user["username"] ) ); - } else { - - $this->restore(); - exit(formatJSEND( "error", "The Username is Already Taken" )); + if( $return > 0 ) { + + $this->username = $user["username"]; + $this->set_default_options(); + //echo formatJSEND( "success", array( "username" => $user["username"] ) ); + } else { + + $this->restore(); + exit(formatJSEND( "error", "The Username is Already Taken" )); + } } } unlink( $users_file ); @@ -415,10 +419,10 @@ class updater { function remove_directory( $path ) { - $files = glob($path . '/*'); - foreach ($files as $file) { - - is_dir($file) ? $this->remove_directory($file) : unlink($file); + $files = glob( $path . '{,.}[!.,!..]*', GLOB_MARK|GLOB_BRACE ); + foreach( $files as $file ) { + + is_dir( $file ) ? $this->remove_directory( $file ) : unlink( $file ); } if( is_dir( $path ) ) { @@ -524,8 +528,6 @@ class updater { $this->copyr( $src, $dest ); $this->remove_directory( $src ); - $this->convert(); - $this->check_sql(); return( "true" ); } catch( Exception $e ) { @@ -534,6 +536,21 @@ class updater { } } + public function update_database() { + + try { + + $this->convert(); + } catch( Exception $e ) { + + $this->restore(); + return( $e ); + } + + $this->check_sql(); + return( "true" ); + } + public function update_option( $option, $value, $user_setting = null ) { $sql = new sql(); @@ -619,6 +636,11 @@ if( isset( $_GET["action"] ) && $_GET["action"] !== '' ) { echo $updater->update(); break; + + case( "update_database" ): + + echo $updater->update_database(); + break; } exit(); @@ -674,6 +696,7 @@ if( isset( $_GET["action"] ) && $_GET["action"] !== '' ) { const codiad = {}; codiad.update = { + base_url: ``, progress: null, init: function() { @@ -711,6 +734,35 @@ if( isset( $_GET["action"] ) && $_GET["action"] !== '' ) { }); }, + apply_database: function() { + + return jQuery.ajax({ + + url: "update.php", + type: "GET", + dataType: 'html', + data: { + action: 'update_database', + }, + + success: function( result ) { + + return result; + }, + + error: function( jqXHR, textStatus, errorThrown ) { + + console.log( 'jqXHR:' ); + console.log( jqXHR ); + console.log( 'textStatus:' ); + console.log( textStatus); + console.log( 'errorThrown:' ); + console.log( errorThrown ); + return null; + } + }); + }, + check_update: function() { this.progress.innerText = "Checking for update ... "; @@ -809,6 +861,33 @@ if( isset( $_GET["action"] ) && $_GET["action"] !== '' ) { update: async function() { + let GET = {}; + let parts = window.location.href.replace(/[?&]+([^=&]+)=([^&]*)/gi, function( m, key, value) { + + GET[key] = value; + }); + + if( Object.keys( GET ).includes( "step" ) && GET.step === "database_update" ) { + + progress.innerText = "Applying database update."; + let apply = await this.apply_database(); + + if( apply !== "true" ) { + + console.log( apply ); + progress.innerText = "Error applying update."; + return; + } + + progress.innerText = "Successfully completed update. Returning you to Codiad ..."; + + setTimeout( function() { + + window.location.href = `${location.protocol}//${codiad.update.base_url}`; + }, 5000); + return; + } + let result = await this.check_update(); console.log( result ); @@ -834,7 +913,7 @@ if( isset( $_GET["action"] ) && $_GET["action"] !== '' ) { return; } - progress.innerText = "Applying update."; + progress.innerText = "Applying filesystem update."; let apply = await this.apply(); if( apply !== "true" ) { @@ -844,7 +923,12 @@ if( isset( $_GET["action"] ) && $_GET["action"] !== '' ) { return; } - progress.innerText = "Update Finished."; + progress.innerText = "Filesystem update finished. Please wait, your browser will now reload and start the datbase update."; + + setTimeout( function() { + + window.location.href = window.location.href + "?step=database_update" + }, 5000); } else if( result === "false" ) { progress.innerText = "No update was found ..."; @@ -856,6 +940,28 @@ if( isset( $_GET["action"] ) && $_GET["action"] !== '' ) { update_development: async function() { + let GET = {}; + let parts = window.location.href.replace(/[?&]+([^=&]+)=([^&]*)/gi, function( m, key, value) { + + GET[key] = value; + }); + + if( Object.keys( GET ).includes( "step" ) && GET.step === "database_update" ) { + + progress.innerText = "Applying database update."; + let apply = await this.apply_database(); + + if( apply !== "true" ) { + + console.log( apply ); + progress.innerText = "Error applying update."; + return; + } + + progress.innerText = "Successfully completed update. You may now return to Codiad."; + return; + } + progress.innerText = "An update was found. Downloading update."; let download = await this.download( true ); @@ -876,7 +982,7 @@ if( isset( $_GET["action"] ) && $_GET["action"] !== '' ) { return; } - progress.innerText = "Applying update."; + progress.innerText = "Applying filesystem update."; let apply = await this.apply(); if( apply !== "true" ) { @@ -886,7 +992,12 @@ if( isset( $_GET["action"] ) && $_GET["action"] !== '' ) { return; } - progress.innerText = "Update Finished."; + progress.innerText = "Filesystem update finished. Please wait, your browser will now reload and start the datbase update."; + + setTimeout( function() { + + window.location.href = window.location.href + "?step=database_update" + }, 5000); }, }; diff --git a/components/user/class.user.php b/components/user/class.user.php index e98b544..78d2586 100755 --- a/components/user/class.user.php +++ b/components/user/class.user.php @@ -41,7 +41,6 @@ class User { public function __construct() { - $this->actives = getJSON( 'active.php' ); } public function add_user() {