A Pen ... FORKED by Martin Novy on CodePen. https://github.com/jcubic/git
Last active
January 24, 2020 09:16
-
-
Save martin12333/f9841643c038eb893edab8c009d5e669 to your computer and use it in GitHub Desktop.
Terminal FS ... https://github.com/jcubic/git
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<div class="term"></div> | |
<textarea class="vi" style="display: none"></textarea> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
BrowserFS.configure({ fs: "IndexedDB", options: {} }, function (err) { | |
window.fs = BrowserFS.BFSRequire("fs"); | |
window.path = BrowserFS.BFSRequire("path"); | |
// -------------------------------------------------------------- | |
function list(path) { | |
term.pause(); | |
return listDir(path).then((list) => (term.resume(), list)); | |
} | |
// -------------------------------------------------------------- | |
function get_path(string) { | |
var path = cwd.replace(/^\//, '').split('/'); | |
if (path[0] === '') { | |
path = path.slice(1); | |
} | |
var parts = string === '/' | |
? string.split('/') | |
: string.replace(/\/?[^\/]*$/, '').split('/'); | |
if (parts[0] === '') { | |
parts = parts.slice(1); | |
} | |
if (string === '/') { | |
return []; | |
} else if (string.startsWith('/')) { | |
return parts; | |
} else if (path.length) { | |
return path.concat(parts); | |
} else { | |
return parts; | |
} | |
} | |
// -------------------------------------------------------------- | |
function read(cmd, cb) { | |
var filename = typeof cmd === 'string' ? cmd : cmd.args.length == 1 ? cwd + '/' + cmd.args[0] : null; | |
if (filename) { | |
fs.readFile(filename, function(err, data) { | |
if (err) { | |
term.error(err.message); | |
} else { | |
cb(data.toString()); | |
} | |
}); | |
} else { | |
term.error('No filename'); | |
} | |
} | |
window.resolve = function resolve(path) { | |
if (path[0] === '/') { | |
return path; | |
} else { | |
return window.path.resolve(window.path.join(cwd, path)); | |
} | |
} | |
// -------------------------------------------------------------- | |
function mkdir(path) { | |
path = resolve(path); | |
return new Promise((resolve, reject) => { | |
fs.stat(path, (err, stat) => { | |
if (err) { | |
fs.mkdir(path, function(err) { | |
if (err) { | |
reject(err.message); | |
} else { | |
resolve(); | |
} | |
}); | |
} else if (stat.isFile()) { | |
reject(`${path} is file`); | |
} else { | |
resolve(); | |
} | |
}); | |
}); | |
} | |
// -------------------------------------------------------------- | |
window.cwd = '/'; | |
var commands = { | |
mkdir: function(cmd) { | |
term.pause(); | |
mkdir(cmd.args[0]).then(term.resume); | |
}, | |
cd: function(cmd) { | |
if (cmd.args.length === 1) { | |
var dirname = path.resolve(cwd + '/' + cmd.args[0]); | |
term.pause(); | |
fs.stat(dirname, (err, stat) => { | |
if (err) { | |
term.error("Directory don't exits").resume(); | |
} else if (stat.isFile()) { | |
term.error(`"${dirname}" is not directory`).resume(); | |
} else { | |
cwd = dirname == '/' ? dirname : dirname.replace(/\/$/, ''); | |
term.resume(); | |
} | |
}); | |
} | |
}, | |
cat: function(cmd) { | |
read(cmd, term.echo); | |
}, | |
less: function(cmd) { | |
read(cmd, term.less.bind(term)); | |
}, | |
ls: function(cmd) { | |
var {options, args} = split_args(cmd.args); | |
function filter(list) { | |
if (options.match(/a/)) { | |
return list; | |
} else if (options.match(/A/)) { | |
return list.filter(name => !name.match(/^\.{1,2}$/)); | |
} else { | |
return list.filter(name => !name.match(/^\./)); | |
} | |
} | |
list(cwd + '/' + (args[0] || '')).then((content) => { | |
var dirs = filter(['.', '..'].concat(content.dirs)).map((dir) => color('blue', dir)); | |
term.echo(dirs.concat(filter(content.files)).join('\n')); | |
}); | |
}, | |
rm: function(cmd) { | |
var {options, args} = split_args(cmd.args); | |
var len = args.length; | |
if (len) { | |
term.pause(); | |
} | |
args.forEach(arg => { | |
var path_name = path.resolve(cwd + '/' + arg); | |
fs.stat(path_name, (err, stat) => { | |
if (err) { | |
term.error(err); | |
} else if (stat) { | |
if (stat.isDirectory()) { | |
if (options.match(/r/)) { | |
rmDir(path_name); | |
} else { | |
term.error(`${path_name} is directory`); | |
} | |
} else if (stat.isFile()) { | |
fs.unlink(path_name); | |
} else { | |
term.error(`${path_name} is invalid`) | |
} | |
if (!--len) { | |
term.resume(); | |
} | |
} | |
}); | |
}); | |
}, | |
vi: function(cmd) { | |
var textarea = $('.vi'); | |
var editor; | |
var fname = cmd.args[0]; | |
term.focus(false); | |
if (fname) { | |
var path = resolve(fname); | |
function open(file) { | |
// we need to replace < and & because jsvi is handling html tags | |
// and don't work properly for raw text | |
textarea.val(file.replace(/</g, '<').replace(/&/g, '&')); | |
editor = window.editor = vi(textarea[0], { | |
color: '#ccc', | |
backgroundColor: '#000', | |
onSave: function() { | |
var file = textarea.val().replace(/&/g, '&').replace(/</g, '<'); | |
fs.writeFile(path, file, function(err, wr) { | |
if (err) { | |
term.error(err.message); | |
} | |
}); | |
}, | |
onExit: term.focus | |
}); | |
} | |
fs.stat(path, (err, stat) => { | |
if (stat && stat.isFile()) { | |
read(cmd, open, true); | |
} else { | |
var dir = path.replace(/[^\/]+$/, ''); | |
fs.stat(dir, (err, stat) => { | |
if (stat && stat.isDirectory()) { | |
open('') | |
} else if (err) { | |
term.error(err.message); | |
} else { | |
term.error(`${dir} directory don't exists`); | |
} | |
}); | |
} | |
}); | |
} | |
} | |
}; | |
// -------------------------------------------------------------- | |
var term = $('.term').terminal((command) => { | |
var cmd = $.terminal.parse_command(command); | |
if (commands[cmd.name]) { | |
commands[cmd.name].call(term, cmd); | |
} | |
}, { | |
checkArity: false, | |
greetings: 'BrowserFS', | |
prompt: function(cb) { | |
var path = color('blue', cwd); | |
cb([ | |
color('green', '[email protected]'), | |
':', | |
path, | |
'$ ' | |
].join('')); | |
}, | |
completion: function(string, cb) { | |
var cmd = $.terminal.parse_command(this.before_cursor()); | |
function processAssets(callback) { | |
var dir = get_path(string); | |
list('/' + dir.join('/')).then(callback); | |
} | |
function prepend(list) { | |
if (string.match(/\//)) { | |
var path = string.replace(/\/[^\/]+$/, '').replace(/\/+$/, ''); | |
return list.map((dir) => path + '/' + dir); | |
} else { | |
return list; | |
} | |
} | |
function trailing(list) { | |
return list.map((dir) => dir + '/'); | |
} | |
if (cmd.name !== string) { | |
switch (cmd.name) { | |
// complete file and directories | |
case 'vi': | |
case 'less': | |
return processAssets(content => cb(prepend(trailing(content.dirs).concat(content.files)))); | |
// complete directories | |
case 'ls': | |
case 'cd': | |
return processAssets(content => cb(prepend(trailing(content.dirs)))); | |
} | |
} | |
cb(Object.keys(commands)); | |
} | |
}); | |
}); | |
// ------------------------------------------------------------------- | |
function color(name, string) { | |
var colors = { | |
blue: '#55f', | |
green: '#4d4', | |
grey: '#999', | |
red: '#A00', | |
yellow: '#FF5', | |
violet: '#a320ce', | |
white: '#fff' | |
} | |
if (colors[name]) { | |
return '[[;' + colors[name] + ';]' + string + ']'; | |
} else { | |
return string; | |
} | |
} | |
// ------------------------------------------------------------------- | |
function listDir(path) { | |
return new Promise(function(resolve, reject) { | |
fs.readdir(path, function(err, dirList) { | |
if (err) { | |
return reject(err); | |
} | |
var result = { | |
files: [], | |
dirs: [] | |
}; | |
var len = dirList.length; | |
if (!len) { | |
resolve(result); | |
} | |
dirList.forEach(function(filename) { | |
var file = (path === '/' ? '' : path) + '/' + filename; | |
fs.stat(file, function(err, stat) { | |
if (stat) { | |
result[stat.isFile() ? 'files' : 'dirs'].push(filename); | |
} | |
if (!--len) { | |
resolve(result); | |
} | |
}); | |
}); | |
}); | |
}); | |
} | |
// ------------------------------------------------------------------- | |
function split_args(args) { | |
return { | |
options: args.filter(arg => arg.match(/^-/)).join('').replace(/-/g, ''), | |
args: args.filter(arg => !arg.match(/^-/)) | |
}; | |
} | |
// this is how you can create backed as replecement for BrowserFS | |
// this url need to point to JSON-RPC service you can easily create | |
// using library for your backend language of choice | |
var json_rpc_url = 'backend.php'; | |
function cb_call(method, args, cb) { | |
$.jrpc(json_rpc_url, method, args, function(resp) { | |
if (resp.error) { | |
cb(resp.error); | |
} else { | |
cb(null, resp.result); | |
} | |
}, function(err, status) { | |
cb(status); | |
}) | |
} | |
// same API as BrowserFS that's based on node.js | |
var backendFS = { | |
readFile: function(path, cb) { | |
cb_call('readFile', [path], cb); | |
}, | |
stat: function(path, cb) { | |
cb_call('stat', [path], function(err, result) { | |
if (err) { | |
cb(err); | |
} else { | |
cb(err, { | |
isFile: function() { | |
return result === 'file'; | |
}, | |
isDirectory: function() { | |
return result === 'directory'; | |
} | |
}); | |
} | |
}); | |
}, | |
readdir: function(path, cb) { | |
cb_call('readdir', [path], cb); | |
} | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<script src="https://code.jquery.com/jquery-3.1.1.min.js"></script> | |
<script src="https://cdn.terminal.jcubic.pl/wcwidth.js"></script> | |
<script src="https://unpkg.com/jquery.terminal/js/jquery.terminal.js"></script> | |
<script src="https://unpkg.com/js-polyfills/keyboard.js"></script> | |
<script src="https://unpkg.com/jquery.terminal/js/unix_formatting.js"></script> | |
<script src="https://unpkg.com/[email protected]/prism.js"></script> | |
<script src="https://unpkg.com/jquery.terminal/js/less.js"></script> | |
<script src="https://unpkg.com/jquery.terminal/js/prism.js"></script> | |
<script src="https://unpkg.com/[email protected]/dist/browserfs.js"></script> | |
<script src="https://unpkg.com/[email protected]/vi.js"></script> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
body { | |
padding: 0; | |
margin: 0; | |
background: #000; | |
min-height: 100vh; | |
} | |
.terminal { | |
--size: 1.2; | |
height: 100vh; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<link href="https://unpkg.com/prismjs/themes/prism-coy.css" rel="stylesheet" /> | |
<link href="https://unpkg.com/jquery.terminal/css/jquery.terminal.css" rel="stylesheet" /> | |
<link href="https://unpkg.com/[email protected]/vi.css" rel="stylesheet" /> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment