noide
Version:
Web Editor built on hapi and nes websockets using browserify, superviews.js, bootstrap and the ACE editor.
1,788 lines (1,598 loc) • 398 kB
JavaScript
(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
var Nes = require('nes/client')
var host = window.location.host
var client = new Nes.Client('ws://' + host)
module.exports = client
},{"nes/client":39}],2:[function(require,module,exports){
var noide = require('../noide')
var client = require('../client')
var fs = require('../fs')
var util = require('../util')
var config = require('../../config/client')
var editor = window.ace.edit('editor')
var $shortcuts = window.jQuery('#keyboard-shortcuts').modal({ show: false })
// Set editor options
editor.setOptions({
enableSnippets: true,
enableBasicAutocompletion: true,
enableLiveAutocompletion: false,
fontSize: config.ace.fontSize
})
function save (file, session) {
fs.writeFile(file.path, session.getValue(), function (err, payload) {
if (err) {
return util.handleError(err)
}
file.stat = payload.stat
noide.markSessionClean(session)
})
}
editor.commands.addCommands([{
name: 'help',
bindKey: {
win: 'Ctrl-H',
mac: 'Command-H'
},
exec: function () {
$shortcuts.modal('show')
},
readOnly: true
}])
editor.setTheme('ace/theme/' + config.ace.theme)
editor.commands.addCommands([{
name: 'save',
bindKey: {
win: 'Ctrl-S',
mac: 'Command-S'
},
exec: function (editor) {
var file = noide.current
var session = noide.getSession(file)
save(file, session)
},
readOnly: false
}, {
name: 'saveall',
bindKey: {
win: 'Ctrl-Shift-S',
mac: 'Command-Option-S'
},
exec: function (editor) {
noide.dirty.forEach(function (session) {
var file = session.file
save(file, session)
})
},
readOnly: false
}, {
name: 'beautify',
bindKey: {
win: 'Ctrl-B',
mac: 'Command-B'
},
exec: function (editor) {
var file = noide.current
var path
if (file) {
switch (file.ext) {
case '.js':
path = '/standard-format'
break
default:
}
if (path) {
client.request({
path: path,
payload: {
value: editor.getValue()
},
method: 'POST'
}, function (err, payload) {
if (err) {
return util.handleError(err)
}
editor.setValue(payload)
})
}
}
},
readOnly: false
}])
module.exports = editor
},{"../../config/client":30,"../client":1,"../fs":7,"../noide":12,"../util":27}],3:[function(require,module,exports){
var page = require('page')
var patch = require('../patch')
var fs = require('../fs')
var view = require('./view.html')
function FileEditor (el) {
var model = {
mode: null,
file: null,
rename: fs.rename,
mkfile: function (path) {
fs.mkfile(path, function (err, payload) {
if (!err) {
// Open the new file. Leave a short delay
// to allow it to register from the socket
setTimeout(function () {
page('/file?path=' + payload.relativePath)
}, 500)
}
})
},
mkdir: fs.mkdir
}
function hide () {
model.file = null
model.mode = null
patch(el, view, model, hide)
}
function show (file, mode) {
model.file = file
model.mode = mode
patch(el, view, model, hide)
var input = el.querySelector('input')
input.focus()
}
this.show = show
}
FileEditor.prototype.rename = function (file) {
this.show(file, 'rename')
}
FileEditor.prototype.mkfile = function (dir) {
this.show(dir, 'mkfile')
}
FileEditor.prototype.mkdir = function (dir) {
this.show(dir, 'mkdir')
}
var fileEditorEl = document.getElementById('file-editor')
var fileEditor = new FileEditor(fileEditorEl)
module.exports = fileEditor
},{"../fs":7,"../patch":14,"./view.html":4,"page":41}],4:[function(require,module,exports){
var IncrementalDOM = require('incremental-dom')
var patch = IncrementalDOM.patch
var elementOpen = IncrementalDOM.elementOpen
var elementVoid = IncrementalDOM.elementVoid
var elementClose = IncrementalDOM.elementClose
var skip = IncrementalDOM.skip
var currentElement = IncrementalDOM.currentElement
var text = IncrementalDOM.text
module.exports = (function () {
var hoisted1 = ["class", "file-editor"]
var hoisted2 = ["class", "form-group"]
var hoisted3 = ["for", "name"]
var hoisted4 = ["class", "input-group"]
var hoisted5 = ["type", "text", "class", "form-control input-sm", "id", "filename"]
var hoisted6 = ["class", "input-group-btn"]
var hoisted7 = ["class", "btn btn-primary btn-sm", "type", "submit"]
var hoisted8 = ["class", "btn btn-secondary btn-sm", "type", "button"]
var hoisted9 = ["class", "form-group"]
var hoisted10 = ["for", "name"]
var hoisted11 = ["class", "input-group"]
var hoisted12 = ["type", "text", "class", "form-control input-sm", "id", "dirname"]
var hoisted13 = ["class", "input-group-btn"]
var hoisted14 = ["class", "btn btn-primary btn-sm", "type", "submit"]
var hoisted15 = ["class", "btn btn-secondary btn-sm", "type", "button"]
var hoisted16 = ["class", "form-group"]
var hoisted17 = ["for", "name"]
var hoisted18 = ["class", "input-group"]
var hoisted19 = ["type", "text", "class", "form-control input-sm", "id", "rename"]
var hoisted20 = ["class", "input-group-btn"]
var hoisted21 = ["class", "btn btn-primary btn-sm", "type", "submit"]
var hoisted22 = ["class", "btn btn-secondary btn-sm", "type", "button"]
return function fileEditor (model, hide) {
var file = model.file
if (file) {
elementOpen("div", null, hoisted1)
if (model.mode === 'mkfile') {
elementOpen("form", "mkfile", null, "onsubmit", function ($event) {
$event.preventDefault();
var $element = this;
model.mkfile(this.filename.value); hide()})
elementOpen("div", null, hoisted2)
elementOpen("label", null, hoisted3)
text("Add new file")
elementClose("label")
elementOpen("div", null, hoisted4)
elementOpen("input", null, hoisted5, "value", file.relativePath ? file.relativePath + '/' : '')
elementClose("input")
elementOpen("span", null, hoisted6)
elementOpen("button", null, hoisted7)
text("OK")
elementClose("button")
elementOpen("button", null, hoisted8, "onclick", function ($event) {
$event.preventDefault();
var $element = this;
hide()})
text("Cancel")
elementClose("button")
elementClose("span")
elementClose("div")
elementClose("div")
elementClose("form")
}
if (model.mode === 'mkdir') {
elementOpen("form", "mkdir", null, "onsubmit", function ($event) {
$event.preventDefault();
var $element = this;
model.mkdir(this.dirname.value); hide()})
elementOpen("div", null, hoisted9)
elementOpen("label", null, hoisted10)
text("Add new folder")
elementClose("label")
elementOpen("div", null, hoisted11)
elementOpen("input", null, hoisted12, "value", file.relativePath ? file.relativePath + '/' : '')
elementClose("input")
elementOpen("span", null, hoisted13)
elementOpen("button", null, hoisted14)
text("OK")
elementClose("button")
elementOpen("button", null, hoisted15, "onclick", function ($event) {
$event.preventDefault();
var $element = this;
hide()})
text("Cancel")
elementClose("button")
elementClose("span")
elementClose("div")
elementClose("div")
elementClose("form")
}
if (model.mode === 'rename') {
elementOpen("form", "rename", null, "onsubmit", function ($event) {
$event.preventDefault();
var $element = this;
model.rename(file.relativePath, this.rename.value); hide()})
elementOpen("div", null, hoisted16)
elementOpen("label", null, hoisted17)
text("Rename")
elementClose("label")
elementOpen("div", null, hoisted18)
elementOpen("input", null, hoisted19, "value", file.relativePath)
elementClose("input")
elementOpen("span", null, hoisted20)
elementOpen("button", null, hoisted21)
text("OK")
elementClose("button")
elementOpen("button", null, hoisted22, "onclick", function ($event) {
$event.preventDefault();
var $element = this;
hide()})
text("Cancel")
elementClose("button")
elementClose("span")
elementClose("div")
elementClose("div")
elementClose("form")
}
elementClose("div")
}
}
})();
},{"incremental-dom":37}],5:[function(require,module,exports){
var patch = require('../patch')
var fs = require('../fs')
var util = require('../util')
var fileEditor = require('../file-editor')
var view = require('./view.html')
var copied
var $ = window.jQuery
var path = require('path')
function FileMenu (el) {
var $el = $(el)
$el.on('mouseleave', function () {
hide()
})
function callback (err, payload) {
if (err) {
return util.handleError(err)
}
}
function resetPasteBuffer () {
copied = null
}
function setPasteBuffer (file, action) {
hide()
copied = {
file: file,
action: action
}
}
function showPaste (file) {
if (copied) {
var sourcePath = copied.file.relativePath.toLowerCase()
var sourceDir = copied.file.relativeDir.toLowerCase()
var destinationDir = (file.isDirectory ? file.relativePath : file.relativeDir).toLowerCase()
var isDirectory = copied.file.isDirectory
if (!isDirectory) {
// Always allow pasteing of a file unless it's a move operation (cut) and the destination dir is the same
return copied.action !== 'cut' || destinationDir !== sourceDir
} else {
// Allow pasteing directories if not into self a decendent
if (destinationDir.indexOf(sourcePath) !== 0) {
// and or if the operation is move (cut) the parent dir too
return copied.action !== 'cut' || destinationDir !== sourceDir
}
}
}
return false
}
function rename (file) {
hide()
resetPasteBuffer()
fileEditor.rename(file)
}
function paste (file) {
hide()
if (copied && copied.file) {
var action = copied.action
var source = copied.file
resetPasteBuffer()
var pastePath = file.isDirectory ? file.path : file.dir
if (action === 'copy') {
fs.copy(source.path, path.resolve(pastePath, source.name), callback)
} else if (action === 'cut') {
fs.rename(source.path, path.resolve(pastePath, source.name), callback)
}
}
}
function mkfile (file) {
hide()
resetPasteBuffer()
fileEditor.mkfile(file.isDirectory ? file : file.parent)
}
function mkdir (file) {
hide()
resetPasteBuffer()
fileEditor.mkdir(file.isDirectory ? file : file.parent)
}
function remove (file) {
var path = file.relativePath
hide()
resetPasteBuffer()
if (window.confirm('Delete [' + path + ']')) {
fs.remove(path, callback)
}
}
var model = {
x: 0,
y: 0,
file: null,
rename: rename,
paste: paste,
mkfile: mkfile,
mkdir: mkdir,
remove: remove,
showPaste: showPaste,
setPasteBuffer: setPasteBuffer
}
function hide () {
model.file = null
patch(el, view, model)
}
function show (x, y, file) {
model.x = x
model.y = y
model.file = file
patch(el, view, model)
}
this.show = show
}
var fileMenuEl = document.getElementById('file-menu')
var fileMenu = new FileMenu(fileMenuEl)
module.exports = fileMenu
},{"../file-editor":3,"../fs":7,"../patch":14,"../util":27,"./view.html":6,"path":31}],6:[function(require,module,exports){
var IncrementalDOM = require('incremental-dom')
var patch = IncrementalDOM.patch
var elementOpen = IncrementalDOM.elementOpen
var elementVoid = IncrementalDOM.elementVoid
var elementClose = IncrementalDOM.elementClose
var skip = IncrementalDOM.skip
var currentElement = IncrementalDOM.currentElement
var text = IncrementalDOM.text
module.exports = (function () {
var hoisted1 = ["class", "dropdown-menu"]
var hoisted2 = ["class", "dropdown-header"]
var hoisted3 = ["class", "fa fa-pencil"]
var hoisted4 = ["class", "divider"]
var hoisted5 = ["class", "fa fa-scissors"]
var hoisted6 = ["class", "fa fa-copy"]
var hoisted7 = ["class", "fa fa-paste"]
var hoisted8 = ["class", "divider"]
var hoisted9 = ["class", "fa fa-file"]
var hoisted10 = ["class", "fa fa-folder"]
var hoisted11 = ["class", "divider"]
var hoisted12 = ["class", "fa fa-trash"]
return function fileMenu (model) {
var file = model.file
if (file) {
elementOpen("ul", null, hoisted1, "style", { top: model.y, left: model.x })
elementOpen("li", "header", hoisted2)
text("" + (file.name) + "")
elementClose("li")
elementOpen("li", "rename")
elementOpen("a", null, null, "onclick", function ($event) {
$event.preventDefault();
var $element = this;
model.rename(file)})
elementOpen("i", null, hoisted3)
elementClose("i")
text(" Rename")
elementClose("a")
elementClose("li")
elementOpen("li", "divider-1", hoisted4)
elementClose("li")
elementOpen("li", "cut")
elementOpen("a", null, null, "onclick", function ($event) {
$event.preventDefault();
var $element = this;
model.setPasteBuffer(file, 'cut')})
elementOpen("i", null, hoisted5)
elementClose("i")
text(" Cut")
elementClose("a")
elementClose("li")
elementOpen("li", "copy")
elementOpen("a", null, null, "onclick", function ($event) {
$event.preventDefault();
var $element = this;
model.setPasteBuffer(file, 'copy')})
elementOpen("i", null, hoisted6)
elementClose("i")
text(" Copy")
elementClose("a")
elementClose("li")
if (model.showPaste(file)) {
elementOpen("li", "paste")
elementOpen("a", null, null, "onclick", function ($event) {
$event.preventDefault();
var $element = this;
model.paste(file)})
elementOpen("i", null, hoisted7)
elementClose("i")
text(" Paste")
elementClose("a")
elementClose("li")
}
elementOpen("li", "divider-2", hoisted8)
elementClose("li")
elementOpen("li", "mkfile")
elementOpen("a", null, null, "onclick", function ($event) {
$event.preventDefault();
var $element = this;
model.mkfile(file)})
elementOpen("i", null, hoisted9)
elementClose("i")
text(" Add new file")
elementClose("a")
elementClose("li")
elementOpen("li", "mkdir")
elementOpen("a", null, null, "onclick", function ($event) {
$event.preventDefault();
var $element = this;
model.mkdir(file)})
elementOpen("i", null, hoisted10)
elementClose("i")
text(" Add new folder")
elementClose("a")
elementClose("li")
elementOpen("li", "divider-3", hoisted11)
elementClose("li")
elementOpen("li", "delete")
elementOpen("a", null, null, "onclick", function ($event) {
$event.preventDefault();
var $element = this;
model.remove(file)})
elementOpen("i", null, hoisted12)
elementClose("i")
text(" Delete")
elementClose("a")
elementClose("li")
elementClose("ul")
}
}
})();
},{"incremental-dom":37}],7:[function(require,module,exports){
var client = require('./client')
function readFile (path, callback) {
client.request({
path: '/readfile?path=' + path,
method: 'GET'
}, callback)
}
function writeFile (path, contents, callback) {
client.request({
path: '/writefile',
payload: {
path: path,
contents: contents
},
method: 'PUT'
}, callback)
}
function mkdir (path, callback) {
client.request({
path: '/mkdir',
payload: {
path: path
},
method: 'POST'
}, callback)
}
function mkfile (path, callback) {
client.request({ path: '/mkfile',
payload: {
path: path
},
method: 'POST'
}, callback)
}
function copy (source, destination, callback) {
client.request({
path: '/copy',
payload: {
source: source,
destination: destination
},
method: 'POST'
}, callback)
}
function rename (oldPath, newPath, callback) {
client.request({
path: '/rename',
payload: {
oldPath: oldPath,
newPath: newPath
},
method: 'PUT'
}, callback)
}
function remove (path, callback) {
client.request({
path: '/remove',
payload: {
path: path
},
method: 'DELETE'
}, callback)
}
module.exports = {
mkdir: mkdir,
mkfile: mkfile,
copy: copy,
readFile: readFile,
writeFile: writeFile,
rename: rename,
remove: remove
}
},{"./client":1}],8:[function(require,module,exports){
var extend = require('extend')
function File (data) {
extend(this, data)
if (this.isDirectory) {
this.expanded = false
}
}
Object.defineProperties(File.prototype, {
isFile: {
get: function () {
return !this.isDirectory
}
}
})
module.exports = File
},{"extend":36}],9:[function(require,module,exports){
var page = require('page')
var qs = require('querystring')
var fs = require('./fs')
var client = require('./client')
var util = require('./util')
var splitter = require('./splitter')
var editor = require('./editor')
var linter = require('./standard')
var noide = require('./noide')
var watch = require('./watch')
var workspacesEl = document.getElementById('workspaces')
window.onbeforeunload = function () {
if (noide.dirty.length) {
return 'Unsaved changes will be lost - are you sure you want to leave?'
}
}
client.connect(function (err) {
if (err) {
return util.handleError(err)
}
client.request('/watched', function (err, payload) {
if (err) {
return util.handleError(err)
}
noide.load(payload)
// Save state on page unload
window.onunload = function () {
noide.saveState()
}
// Build the tree pane
require('./tree')
// Build the recent list pane
require('./recent')
// Build the procsses pane
var processesView = require('./processes')
// Subscribe to watched file changes
// that happen on the file system
watch()
// Subscribe to editor changes and
// update the recent files views
editor.on('input', function () {
noide.onInput()
})
/* Initialize the splitters */
function resizeEditor () {
editor.resize()
processesView.editor.resize()
}
splitter(document.getElementById('sidebar-workspaces'), false, resizeEditor)
splitter(document.getElementById('workspaces-info'), true, resizeEditor)
splitter(document.getElementById('main-footer'), true, resizeEditor)
/* Initialize the standardjs linter */
linter()
function setWorkspace (className) {
workspacesEl.className = className || 'welcome'
}
page('*', function (ctx, next) {
// Update current file state
noide.current = null
next()
})
page('/', function (ctx) {
setWorkspace()
})
page('/file', function (ctx, next) {
var relativePath = qs.parse(ctx.querystring).path
var file = noide.getFile(relativePath)
if (!file) {
return next()
}
var session = noide.getSession(file)
function setSession () {
setWorkspace('editor')
// Update state
noide.current = file
if (!noide.hasRecent(file)) {
noide.addRecent(file)
}
// Set the editor session
editor.setSession(session.editSession)
editor.resize()
editor.focus()
}
if (session) {
setSession()
} else {
fs.readFile(relativePath, function (err, payload) {
if (err) {
return util.handleError(err)
}
session = noide.addSession(file, payload.contents)
setSession()
})
}
})
page('*', function (ctx) {
setWorkspace('not-found')
})
page({
hashbang: true
})
})
})
},{"./client":1,"./editor":2,"./fs":7,"./noide":12,"./processes":16,"./recent":21,"./splitter":23,"./standard":24,"./tree":25,"./util":27,"./watch":28,"page":41,"querystring":35}],10:[function(require,module,exports){
var util = require('./util')
var client = require('./client')
function run (command, name, callback) {
if (typeof name === 'function') {
callback = name
name = command
}
if (!name) {
name = command
}
client.request({
path: '/io',
payload: {
name: name,
command: command
},
method: 'POST'
}, function (err, payload) {
if (err) {
util.handleError(err)
}
callback && callback(err, payload)
})
}
module.exports = {
run: run
}
},{"./client":1,"./util":27}],11:[function(require,module,exports){
module.exports = function (file) {
var modes = {
'.js': 'ace/mode/javascript',
'.css': 'ace/mode/css',
'.scss': 'ace/mode/scss',
'.less': 'ace/mode/less',
'.html': 'ace/mode/html',
'.htm': 'ace/mode/html',
'.ejs': 'ace/mode/html',
'.json': 'ace/mode/json',
'.md': 'ace/mode/markdown',
'.coffee': 'ace/mode/coffee',
'.jade': 'ace/mode/jade',
'.php': 'ace/mode/php',
'.py': 'ace/mode/python',
'.sass': 'ace/mode/sass',
'.txt': 'ace/mode/text',
'.typescript': 'ace/mode/typescript',
'.gitignore': 'ace/mode/gitignore',
'.xml': 'ace/mode/xml'
}
return modes[file.ext]
}
},{}],12:[function(require,module,exports){
var page = require('page')
var Fso = require('./fso')
var Session = require('./session')
var observable = require('./observable')
var storageKey = 'noide'
// The current active file
var current
// The map of files
var files = null
// The map of recent files
var recent = new Map()
// The map of sessions
var sessions = new Map()
var noide = {
get current () {
return current
},
set current (value) {
current = value
this.emitChangeCurrent({
detail: current
})
},
load: function (payload) {
this.cwd = payload.cwd
files = new Map(payload.watched.map(function (item) {
return [item.relativePath, new Fso(item)]
}))
var storage = window.localStorage.getItem(storageKey)
storage = storage ? JSON.parse(storage) : {}
var file, i
if (storage.recent) {
for (i = 0; i < storage.recent.length; i++) {
file = files.get(storage.recent[i])
if (file) {
recent.set(file, false)
}
}
}
if (storage.expanded) {
for (i = 0; i < storage.expanded.length; i++) {
file = files.get(storage.expanded[i])
if (file && file.isDirectory) {
file.expanded = true
}
}
}
},
saveState: function (files) {
var storage = {
recent: this.recent.map(function (item) {
return item.relativePath
}),
expanded: this.files.filter(function (item) {
return item.expanded
}).map(function (item) {
return item.relativePath
})
}
window.localStorage.setItem(storageKey, JSON.stringify(storage))
},
get files () {
return Array.from(files.values())
},
get sessions () {
return Array.from(sessions.values())
},
get recent () {
return Array.from(recent.keys())
},
get dirty () {
return this.sessions.filter(function (item) {
return item.isDirty
})
},
addFile: function (data) {
var file = new Fso(data)
files.set(file.relativePath, file)
this.emitAddFile(file)
return file
},
getFile: function (path) {
return files.get(path)
},
hasFile: function (path) {
return files.has(path)
},
hasRecent: function (file) {
return recent.has(file)
},
addRecent: function (file) {
recent.set(file, true)
this.emitAddRecent(file)
},
getSession: function (file) {
return sessions.get(file)
},
getRecent: function (file) {
return recent.get(file)
},
hasSession: function (file) {
return sessions.has(file)
},
addSession: function (file, contents) {
var session = new Session(file, contents)
sessions.set(file, session)
return session
},
markSessionClean: function (session) {
session.markClean()
this.emitChangeSessionDirty(session)
},
onInput: function () {
var file = noide.current
var session = this.getSession(file)
if (session) {
var isClean = session.isClean
if (isClean && session.isDirty) {
session.isDirty = false
this.emitChangeSessionDirty(session)
} else if (!isClean && !session.isDirty) {
session.isDirty = true
this.emitChangeSessionDirty(session)
}
}
},
removeFile: function (file) {
// Remove from files
if (this.hasFile(file.relativePath)) {
files.delete(file.relativePath)
this.emitRemoveFile(file)
}
// Remove session
if (this.hasSession(file)) {
sessions.delete(file)
}
// Remove from recent files
if (this.hasRecent(file)) {
recent.delete(file)
this.emitRemoveRecent(file)
}
// If it's the current file getting removed,
// navigate back to the previous session/file
if (current === file) {
if (sessions.size) {
// Open the first session
page('/file?path=' + this.sessions[0].file.relativePath)
} else if (recent.size) {
// Open the first recent file
page('/file?path=' + this.recent[0].relativePath)
} else {
page('/')
}
}
},
closeFile: function (file) {
var session = this.getSession(file)
var close = session && session.isDirty
? window.confirm('There are unsaved changes to this file. Are you sure?')
: true
if (close) {
// Remove from recent files
recent.delete(file)
this.emitRemoveRecent(file)
if (session) {
// Remove session
sessions.delete(file)
// If it's the current file getting closed,
// navigate back to the previous session/file
if (current === file) {
if (sessions.size) {
// Open the first session
page('/file?path=' + this.sessions[0].file.relativePath)
} else if (recent.size) {
// Open the first recent file
page('/file?path=' + this.recent[0].relativePath)
} else {
page('/')
}
}
}
}
}
}
window.noide = noide
module.exports = observable(noide, {
'Change': 'change',
'AddFile': 'addfile',
'RemoveFile': 'removefile',
'AddRecent': 'addRecent',
'RemoveRecent': 'removerecent',
'ChangeCurrent': 'changecurrent',
'ChangeSessionDirty': 'changesessiondirty'
})
},{"./fso":8,"./observable":13,"./session":22,"page":41}],13:[function(require,module,exports){
var $ = window.jQuery
function Observable (eventsObj) {
var _listeners = {}
function listeners (event) {
return event ? _listeners[event] || (_listeners[event] = $.Callbacks()) : _listeners
}
var on = function (event, fn) {
listeners(event).add(fn)
}
var off = function (event, fn) {
listeners(event).remove(fn)
}
var emit = function (event, data) {
listeners(event).fireWith(this, [data])
}
// Add event attach/detatch handler functions
var events = Object.keys(eventsObj)
events.forEach(function (event) {
this['on' + event] = function (fn) {
on.call(this, eventsObj[event], fn)
}
this['off' + event] = function (fn) {
off.call(this, eventsObj[event], fn)
}
this['emit' + event] = function (data) {
data.type = eventsObj[event]
emit.call(this, eventsObj[event], data)
}
}, this)
// this.on = on
// this.off = off
// this.emit = emit
this.events = eventsObj
}
/*
* Make a Constructor become an observable and
* exposes event names as constants
*/
// module.exports = function (Constructor, events) {
// /*
// * Mixin Observable into Constructor prototype
// */
// var p = typeof Constructor === 'function' ? Constructor.prototype : Constructor
// $.extend(p, new Observable(events))
//
// return Constructor
// }
module.exports = function (ctorOrObj, events) {
if (!ctorOrObj) {
return new Observable(events)
}
/*
* Mixin Observable into Constructor prototype
*/
var p = typeof ctorOrObj === 'function' ? ctorOrObj.prototype : ctorOrObj
$.extend(p, new Observable(events))
return ctorOrObj
}
},{}],14:[function(require,module,exports){
var IncrementalDOM = require('incremental-dom')
var patch = IncrementalDOM.patch
// Fix up the element `value` attribute
IncrementalDOM.attributes.value = function (el, name, value) {
el.value = value
}
module.exports = function (el, view, data) {
var args = Array.prototype.slice.call(arguments)
if (args.length <= 3) {
patch(el, view, data)
} else {
patch(el, function () {
view.apply(this, args.slice(2))
})
}
}
},{"incremental-dom":37}],15:[function(require,module,exports){
var IncrementalDOM = require('incremental-dom')
var patch = IncrementalDOM.patch
var elementOpen = IncrementalDOM.elementOpen
var elementVoid = IncrementalDOM.elementVoid
var elementClose = IncrementalDOM.elementClose
var skip = IncrementalDOM.skip
var currentElement = IncrementalDOM.currentElement
var text = IncrementalDOM.text
module.exports = (function () {
var hoisted1 = ["class", "control"]
var hoisted2 = ["class", "input-group"]
var hoisted3 = ["class", "input-group-btn dropup"]
var hoisted4 = ["type", "button", "class", "btn btn-default btn-sm dropdown-toggle", "data-toggle", "dropdown"]
var hoisted5 = ["class", "caret"]
var hoisted6 = ["class", "dropdown-menu"]
var hoisted7 = ["class", "dropdown-header"]
var hoisted8 = ["href", "#"]
var hoisted9 = ["type", "text", "class", "form-control input-sm", "name", "command", "required", "", "autocomplete", "off", "placeholder", ">"]
var hoisted10 = ["class", "input-group-btn"]
var hoisted11 = ["class", "btn btn-default btn-sm", "type", "submit", "title", "Run command"]
var hoisted12 = ["class", "btn btn-success btn-sm", "type", "button", "title", "Remove dead processes"]
var hoisted13 = ["class", "nav nav-tabs"]
var hoisted14 = ["class", "dropdown-toggle", "data-toggle", "dropdown"]
var hoisted15 = ["class", "caret"]
var hoisted16 = ["class", "dropdown-menu"]
var hoisted17 = ["class", "dropdown-header"]
var hoisted18 = ["class", "dropdown-header"]
var hoisted19 = ["role", "separator", "class", "divider"]
var hoisted20 = ["class", "fa fa-stop"]
var hoisted21 = ["class", "fa fa-refresh"]
var hoisted22 = ["class", "fa fa-close"]
var hoisted23 = ["class", "processes"]
var hoisted24 = ["class", "output"]
return function description (model, actions, editorIsInitialized) {
elementOpen("div", null, hoisted1)
elementOpen("form", null, null, "onsubmit", function ($event) {
$event.preventDefault();
var $element = this;
actions.run(this.command.value)})
elementOpen("div", null, hoisted2)
elementOpen("div", null, hoisted3)
elementOpen("button", null, hoisted4)
text("Task ")
elementOpen("span", null, hoisted5)
elementClose("span")
elementClose("button")
elementOpen("ul", null, hoisted6)
if (!model.tasks.length) {
elementOpen("li", null, hoisted7)
text("No npm run-scripts found")
elementClose("li")
}
if (model.tasks.length) {
;(Array.isArray(model.tasks) ? model.tasks : Object.keys(model.tasks)).forEach(function(task, $index) {
elementOpen("li", task.name)
elementOpen("a", null, hoisted8, "onclick", function ($event) {
$event.preventDefault();
var $element = this;
actions.setCommand('npm run ' + task.name)})
text("" + (task.name) + "")
elementClose("a")
elementClose("li")
}, model.tasks)
}
elementClose("ul")
elementClose("div")
elementOpen("input", null, hoisted9, "value", model.command)
elementClose("input")
elementOpen("span", null, hoisted10)
elementOpen("button", null, hoisted11)
text("Run")
elementClose("button")
if (model.dead.length) {
elementOpen("button", null, hoisted12, "onclick", function ($event) {
$event.preventDefault();
var $element = this;
actions.removeAllDead()})
text("Clear completed")
elementClose("button")
}
elementClose("span")
elementClose("div")
elementClose("form")
elementOpen("ul", null, hoisted13)
;(Array.isArray(model.processes) ? model.processes : Object.keys(model.processes)).forEach(function(process, $index) {
elementOpen("li", process.pid, null, "class", process === model.current ? 'dropup active' : 'dropup')
elementOpen("a", null, hoisted14, "onclick", function ($event) {
$event.preventDefault();
var $element = this;
actions.setCurrent(process)})
elementOpen("span", null, null, "class", 'circle ' + (!process.isAlive ? 'dead' : (process.isActive ? 'alive active' : 'alive')))
elementClose("span")
text(" \
" + (process.name || process.command) + " \
")
elementOpen("span", null, hoisted15)
elementClose("span")
elementClose("a")
elementOpen("ul", null, hoisted16)
if (process.isAlive) {
elementOpen("li", null, hoisted17)
text("Process [" + (process.pid) + "]")
elementClose("li")
}
if (!process.isAlive) {
elementOpen("li", null, hoisted18)
text("Process [")
elementOpen("s")
text("" + (process.pid) + "")
elementClose("s")
text("]")
elementClose("li")
}
elementOpen("li", null, hoisted19)
elementClose("li")
elementOpen("li")
if (process.isAlive) {
elementOpen("a", "kill-tab", null, "onclick", function ($event) {
$event.preventDefault();
var $element = this;
actions.kill(process)})
elementOpen("i", null, hoisted20)
elementClose("i")
text(" Stop")
elementClose("a")
}
elementClose("li")
if (!process.isAlive) {
elementOpen("li")
elementOpen("a", "resurrect-tab", null, "onclick", function ($event) {
$event.preventDefault();
var $element = this;
actions.resurrect(process)})
elementOpen("i", null, hoisted21)
elementClose("i")
text(" Resurrect")
elementClose("a")
elementClose("li")
elementOpen("li")
elementOpen("a", "remove-tab", null, "onclick", function ($event) {
$event.preventDefault();
var $element = this;
actions.remove(process)})
elementOpen("i", null, hoisted22)
elementClose("i")
text(" Close")
elementClose("a")
elementClose("li")
}
elementClose("ul")
elementClose("li")
}, model.processes)
elementClose("ul")
elementClose("div")
elementOpen("div", null, hoisted23, "style", {display: model.current ? '' : 'none'})
elementOpen("div", null, hoisted24)
if (editorIsInitialized) {
skip()
} else {
}
elementClose("div")
elementClose("div")
}
})();
},{"incremental-dom":37}],16:[function(require,module,exports){
var patch = require('../patch')
var view = require('./index.html')
var model = require('./model')
var Task = require('./task')
var Process = require('./process')
var client = require('../client')
var io = require('../io')
var fs = require('../fs')
var util = require('../util')
var config = require('../../config/client')
function Processes (el) {
var editor, commandEl
/**
* Sets the isActive state on the process.
* Processes are activated when they receive some data.
* After a short delay, this is reset to inactive.
*/
function setProcessActiveState (process, value) {
if (process.isActive !== value) {
var timeout = process._timeout
if (timeout) {
clearTimeout(timeout)
}
process.isActive = value
if (process.isActive) {
process._timeout = setTimeout(function () {
setProcessActiveState(process, false)
}, 1500)
}
render()
}
}
client.subscribe('/io', function (payload) {
var process = model.findProcessByPid(payload.pid)
if (process) {
var session = process.session
// Insert data chunk
session.insert({
row: session.getLength(),
column: 0
}, payload.data)
// Move to the end of the output
session.getSelection().moveCursorFileEnd()
// Set the process active state to true
setProcessActiveState(process, true)
render()
}
}, function (err) {
if (err) {
return util.handleError(err)
}
})
var actions = {
run: function (command, name) {
io.run(command, name, function (err, payload) {
if (err) {
return util.handleError(err)
}
})
},
remove: function (process) {
if (model.remove(process)) {
render()
}
},
removeAllDead: function () {
var died = model.removeAllDead()
if (died.length) {
render()
}
},
resurrect: function (process) {
model.remove(process)
this.run(process.command, process.name)
render()
},
kill: function (process) {
client.request({
method: 'POST',
path: '/io/kill',
payload: {
pid: process.pid
}
}, function (err, payload) {
if (err) {
util.handleError(err)
}
})
},
setCommand: function (command) {
model.command = command
render()
commandEl.focus()
},
setCurrent: function (process) {
model.current = process
if (model.current) {
editor.setSession(model.current.session)
}
render()
}
}
function loadPids (procs) {
var proc
var born = []
// Find any new processes
for (var i = 0; i < procs.length; i++) {
proc = procs[i]
var process = model.processes.find(function (item) {
return item.pid === proc.pid
})
if (!process) {
// New child process found. Add it
// and set it's cached buffer into session
process = new Process(proc)
process.session.setValue(proc.buffer)
born.push(process)
}
}
// Shut down processes that have died
model.processes.forEach(function (item) {
var match = procs.find(function (check) {
return item.pid === check.pid
})
if (!match) {
// item.pid = 0
item.isAlive = false
setProcessActiveState(item, false)
}
})
// Insert any new child processes
if (born.length) {
Array.prototype.push.apply(model.processes, born)
actions.setCurrent(born[0])
}
render()
}
client.subscribe('/io/pids', loadPids, function (err) {
if (err) {
return util.handleError(err)
}
})
client.request({
path: '/io/pids'
}, function (err, payload) {
if (err) {
util.handleError(err)
}
loadPids(payload)
})
fs.readFile('package.json', function (err, payload) {
if (err) {
return
}
var pkg = {}
try {
pkg = JSON.parse(payload.contents)
} catch (e) {}
console.log(pkg)
if (pkg.scripts) {
var tasks = []
for (var script in pkg.scripts) {
if (script.substr(0, 3) === 'pre' || script.substr(0, 4) === 'post') {
continue
}
tasks.push(new Task({
name: script,
command: pkg.scripts[script]
}))
}
model.tasks = tasks
render()
}
})
function render () {
patch(el, view, model, actions, !!editor)
if (!editor) {
var outputEl = el.querySelector('.output')
commandEl = el.querySelector('input[name="command"]')
editor = window.ace.edit(outputEl)
// Set editor options
editor.setTheme('ace/theme/terminal')
editor.setReadOnly(true)
editor.setFontSize(config.ace.fontSize)
editor.renderer.setShowGutter(false)
editor.setHighlightActiveLine(false)
editor.setShowPrintMargin(false)
}
}
this.model = model
this.render = render
Object.defineProperties(this, {
editor: {
get: function () {
return editor
}
}
})
}
var processesEl = document.getElementById('processes')
var processesView = new Processes(processesEl)
processesView.render()
module.exports = processesView
},{"../../config/client":30,"../client":1,"../fs":7,"../io":10,"../patch":14,"../util":27,"./index.html":15,"./model":17,"./process":18,"./task":19}],17:[function(require,module,exports){
var model = {
tasks: [],
command: '',
processes: [],
current: null,
get dead () {
return this.processes.filter(function (item) {
return !item.isAlive
})
},
remove: function (process) {
var processes = this.processes
var idx = processes.indexOf(process)
if (idx > -1) {
processes.splice(processes.indexOf(process), 1)
if (this.current === process) {
this.current = processes[0]
}
return true
}
return false
},
removeAllDead: function () {
var dead = this.dead
for (var i = 0; i < dead.length; i++) {
this.remove(dead[i])
}
return dead
},
findProcessByPid: function (pid) {
return this.processes.find(function (item) {
return item.pid === pid
})
}
}
module.exports = model
},{}],18:[function(require,module,exports){
var extend = require('extend')
var EditSession = window.ace.require('ace/edit_session').EditSession
function Process (data) {
extend(this, data)
var editSession = new EditSession('', 'ace/mode/sh')
editSession.setUseWorker(false)
this.session = editSession
this.isAlive = true
this.isActive = false
}
module.exports = Process
},{"extend":36}],19:[function(require,module,exports){
function Task (data) {
this.name = data.name
this.command = data.command
}
module.exports = Task
},{}],20:[function(require,module,exports){
var IncrementalDOM = require('incremental-dom')
var patch = IncrementalDOM.patch
var elementOpen = IncrementalDOM.elementOpen
var elementVoid = IncrementalDOM.elementVoid
var elementClose = IncrementalDOM.elementClose
var skip = IncrementalDOM.skip
var currentElement = IncrementalDOM.currentElement
var text = IncrementalDOM.text
module.exports = (function () {
var hoisted1 = ["class", "list-group"]
var hoisted2 = ["class", "close"]
var hoisted3 = ["class", "name icon icon-file-text"]
var hoisted4 = ["class", "file-name"]
var hoisted5 = ["class", "list-group-item-text"]
return function recent (files, current, onClickClose, isDirty) {
elementOpen("div", null, hoisted1, "style", {display: files.length ? '' : 'none'})
;(Array.isArray(files.reverse()) ? files.reverse() : Object.keys(files.reverse())).forEach(function(file, $index) {
elementOpen("a", file.relativePath, null, "title", file.relativePath, "href", "/file?path=" + (file.relativePath) + "", "class", 'list-group-item' + (file === current ? ' active' : ''))
elementOpen("span", null, hoisted2, "onclick", function ($event) {
$event.preventDefault();
var $element = this;
onClickClose(file)})
text("×")
elementClose("span")
elementOpen("span", null, hoisted3, "data-name", file.name, "data-path", file.relativePath)
elementClose("span")
elementOpen("small", null, hoisted4)
text("" + (file.name) + "")
elementClose("small")
if (isDirty(file)) {
elementOpen("span")
text("*")
elementClose("span")
}
if (false) {
elementOpen("p", null, hoisted5)
text("" + ('./' + (file.relativePath !== file.name ? file.relativeDir : '')) + "")
elementClose("p")
}
elementClose("a")
}, files.reverse())
elementClose("div")
}
})();
},{"incremental-dom":37}],21:[function(require,module,exports){
var noide = require('../noide')
var patch = require('../patch')
var view = require('./index.html')
function Recent (el) {
function onClickClose (file) {
noide.closeFile(file)
}
function isDirty (file) {
var session = noide.getSession(file)
return session && session.isDirty
}
function render () {
console.log('Render recent')
patch(el, view, noide.recent, noide.current, onClickClose, isDirty)
}
noide.onAddRecent(render)
noide.onRemoveRecent(render)
noide.onChangeCurrent(render)
noide.onChangeSessionDirty(render)
render()
}
var recentEl = document.getElementById('recent')
var recentView = new Recent(recentEl)
module.exports = recentView
},{"../noide":12,"../patch":14,"./index.html":20}],22:[function(require,module,exports){
var config = require('../config/client')
var modes = require('./modes')
var EditSession = window.ace.require('ace/edit_session').EditSession
var UndoManager = window.ace.require('ace/undomanager').UndoManager
function Session (file, contents) {
var editSession = new EditSession(contents, modes(file))
editSession.setMode(modes(file))
editSession.setUseWorker(false)
editSession.setTabSize(config.ace.tabSize)
editSession.setUseSoftTabs(config.ace.useSoftTabs)
editSession.setUndoManager(new UndoManager())
this.file = file
this.editSession = editSession
}
Session.prototype.markClean = function () {
this.isDirty = false
this.editSession.getUndoManager().markClean()
}
Session.prototype.getValue = function () {
return this.editSession.getValue()
}
Session.prototype.setValue = function (content, markClean) {
this.editSession.setValue(content)
if (markClean) {
this.markClean()
}
}
Object.defineProperties(Session.prototype, {
isClean: {
get: function () {
return this.editSession.getUndoManager().isClean()
}
}
})
module.exports = Session
},{"../config/client":30,"./modes":11}],23:[function(require,module,exports){
var w = window
var d = document
function splitter (handle, collapseNextElement, onEndCallback) {
var last
var horizontal = handle.classList.contains('horizontal')
var el1 = handle.previousElementSibling
var el2 = handle.nextElementSibling
var collapse = collapseNextElement ? el2 : el1
var toggle = document.createElement('a')
toggle.classList.add('toggle')
handle.appendChild(toggle)
function onDrag (e) {
if (horizontal) {
var hT, hB
var hDiff = e.clientY - last
hT = d.defaultView.getComputedStyle(el1, '').getPropertyValue('height')
hB = d.defaultView.getComputedStyle(el2, '').getPropertyValue('height')
hT = parseInt(hT, 10) + hDiff
hB = parseInt(hB, 10) - hDiff
el1.style.height = hT + 'px'
el2.style.height = hB + 'px'
last = e.clientY
} else {
var wL, wR
var wDiff = e.clientX - last
wL = d.defaultView.getComputedStyle(el1, '').getPropertyValue('width')
wR = d.defaultView.getComputedStyle(el2, '').getPropertyValue('width')
wL = parseInt(wL, 10) + wDiff
wR = parseInt(wR, 10) - wDiff
el1.style.width = wL + 'px'
el2.style.width = wR + 'px'
last = e.clientX
}
}
function onEndDrag (e) {
e.preventDefault()
w.removeEventListener('mousemove', onDrag)
w.removeEventListener('mouseup', onEndDrag)
if (onEndCallback) {
onEndCallback()
}
// noide.editor.resize()
// var processes = require('./processes')
// processes.editor.resize()
}
handle.addEventListener('mousedown', function (e) {
e.preventDefault()
if (e.target === toggle) {
collapse.style.display = collapse.style.display === 'none' ? 'block' : 'none'
} else if (collapse.style.display !== 'none') {
last = horizontal ? e.clientY : e.clientX
w.addEventListener('mousemove', onDrag)
w.addEventListener('mouseup', onEndDrag)
}
})
// handle.addEventListener('click', function (e) {
// e.preventDefault()