2015-02-12 15:12:19 +03:00
|
|
|
<!DOCTYPE html>
|
|
|
|
|
<html>
|
2015-09-05 02:52:51 +03:00
|
|
|
<link rel="stylesheet" href="../../css/widget/browse.css">
|
2015-02-12 15:12:19 +03:00
|
|
|
<style>
|
|
|
|
|
|
2015-09-09 17:24:41 +03:00
|
|
|
|
|
|
|
|
.browse-widget .list {
|
|
|
|
|
max-height: 50vh;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2015-06-20 20:51:43 +03:00
|
|
|
/* scrollbar setup... */
|
2015-06-18 00:13:30 +03:00
|
|
|
::-webkit-scrollbar {
|
|
|
|
|
width: 10px;
|
2015-06-19 19:13:11 +03:00
|
|
|
height: 10px;
|
2015-06-18 00:13:30 +03:00
|
|
|
}
|
|
|
|
|
::-webkit-scrollbar-button {
|
|
|
|
|
display: none;
|
|
|
|
|
}
|
|
|
|
|
::-webkit-scrollbar-track {
|
|
|
|
|
}
|
|
|
|
|
::-webkit-scrollbar-track-piece {
|
2015-06-19 19:13:11 +03:00
|
|
|
background: transparent;
|
2015-06-18 00:13:30 +03:00
|
|
|
}
|
2015-06-19 19:13:11 +03:00
|
|
|
::-webkit-scrollbar-track-piece {
|
2015-06-18 00:13:30 +03:00
|
|
|
background: rgba(0, 0, 0, 0.05);
|
|
|
|
|
}
|
2015-06-19 19:13:11 +03:00
|
|
|
::-webkit-scrollbar-thumb {
|
|
|
|
|
background: rgba(0, 0, 0, 0.15);
|
|
|
|
|
}
|
2015-06-18 00:13:30 +03:00
|
|
|
::-webkit-scrollbar-thumb:hover {
|
|
|
|
|
background: rgba(0, 0, 0, 0.3);
|
|
|
|
|
}
|
|
|
|
|
::-webkit-scrollbar-corner {
|
|
|
|
|
}
|
|
|
|
|
::-webkit-resizer {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2015-06-20 20:51:43 +03:00
|
|
|
/* testbed... */
|
|
|
|
|
|
|
|
|
|
.container {
|
|
|
|
|
display: inline-block;
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
|
|
|
|
top: 100px;
|
2015-06-26 03:14:23 +03:00
|
|
|
left: 300px;
|
2015-06-20 20:51:43 +03:00
|
|
|
|
|
|
|
|
box-shadow: rgba(0,0,0,0.5) 0.1em 0.1em 0.4em;
|
|
|
|
|
|
|
|
|
|
/* make the container expand only to a certain size, then scroll */
|
|
|
|
|
/* XXX need to:
|
|
|
|
|
- shorten path to fit width
|
|
|
|
|
i.e. manage width manually when at max-width...
|
|
|
|
|
*/
|
|
|
|
|
max-height: 60vh;
|
|
|
|
|
max-width: 60vw;
|
|
|
|
|
height: auto;
|
|
|
|
|
width: auto;
|
|
|
|
|
overflow-y: auto;
|
|
|
|
|
overflow-x: hidden;
|
|
|
|
|
}
|
|
|
|
|
|
2015-06-21 16:12:20 +03:00
|
|
|
.container.flat {
|
2015-06-26 03:14:23 +03:00
|
|
|
left: 700px;
|
2015-06-21 16:12:20 +03:00
|
|
|
}
|
2015-06-20 20:51:43 +03:00
|
|
|
|
|
|
|
|
|
2015-06-22 17:22:01 +03:00
|
|
|
.container.flat2 {
|
2015-06-26 03:14:23 +03:00
|
|
|
left: 700px;
|
2015-06-22 17:22:01 +03:00
|
|
|
top: 400px;
|
|
|
|
|
}
|
|
|
|
|
|
2015-08-29 21:41:46 +03:00
|
|
|
.container.pathlist {
|
|
|
|
|
left: 700px;
|
|
|
|
|
top: 600px;
|
|
|
|
|
}
|
2015-06-22 17:22:01 +03:00
|
|
|
|
2015-06-20 20:51:43 +03:00
|
|
|
|
2015-06-20 21:28:09 +03:00
|
|
|
|
|
|
|
|
|
|
|
|
|
/* theaming... */
|
|
|
|
|
|
|
|
|
|
body.dark {
|
|
|
|
|
background: black;
|
|
|
|
|
color: white;
|
|
|
|
|
}
|
|
|
|
|
.dark ::-webkit-scrollbar-track-piece {
|
|
|
|
|
background: rgba(255, 255, 255, 0.05);
|
|
|
|
|
}
|
|
|
|
|
.dark ::-webkit-scrollbar-thumb {
|
|
|
|
|
background: rgba(255, 255, 255, 0.15);
|
|
|
|
|
}
|
|
|
|
|
.dark ::-webkit-scrollbar-thumb:hover {
|
|
|
|
|
background: rgba(255, 255, 255, 0.3);
|
|
|
|
|
}
|
|
|
|
|
.dark .container {
|
|
|
|
|
border: solid 1px rgba(255, 255, 255, 0.2);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2015-02-12 15:12:19 +03:00
|
|
|
</style>
|
|
|
|
|
|
2015-09-05 02:52:51 +03:00
|
|
|
<script src="../../ext-lib/jquery.js"></script>
|
|
|
|
|
<script src="../../ext-lib/jquery-ui.js"></script>
|
2015-02-12 15:12:19 +03:00
|
|
|
|
2015-09-05 02:52:51 +03:00
|
|
|
<script src="../jli.js"></script>
|
|
|
|
|
<script src="../toggler.js"></script>
|
2015-02-12 15:12:19 +03:00
|
|
|
|
2015-09-05 02:52:51 +03:00
|
|
|
<script src="../../ext-lib/require.js"></script>
|
2015-02-12 19:07:13 +03:00
|
|
|
|
2015-03-21 16:45:20 +03:00
|
|
|
|
2015-06-20 14:50:54 +03:00
|
|
|
<!--script src="browse-dialog.js"></script-->
|
2015-03-21 16:45:20 +03:00
|
|
|
|
2015-02-12 15:12:19 +03:00
|
|
|
<script>
|
|
|
|
|
|
|
|
|
|
var TREE = {
|
|
|
|
|
dir_a: {},
|
2015-11-26 02:34:23 +03:00
|
|
|
'dir b': {
|
2015-02-12 15:12:19 +03:00
|
|
|
file1: 'this is a file',
|
|
|
|
|
file2: 'this is a file',
|
|
|
|
|
file3: 'this is a file',
|
|
|
|
|
},
|
|
|
|
|
dir_c: {
|
|
|
|
|
file1: 'this is a file',
|
|
|
|
|
dir_b: {
|
|
|
|
|
file1: 'this is a file',
|
|
|
|
|
},
|
2015-03-03 05:27:47 +03:00
|
|
|
dir_c: {},
|
|
|
|
|
dir_d: {},
|
|
|
|
|
dir_e: {},
|
|
|
|
|
dir_f: {},
|
|
|
|
|
dir_g: {},
|
|
|
|
|
dir_h: {},
|
|
|
|
|
dir_i: {},
|
|
|
|
|
dir_j: {},
|
|
|
|
|
dir_k: {},
|
|
|
|
|
dir_l: {},
|
|
|
|
|
dir_m: {},
|
|
|
|
|
dir_o: {},
|
|
|
|
|
dir_p: {},
|
|
|
|
|
dir_q: {},
|
|
|
|
|
dir_r: {},
|
|
|
|
|
dir_s: {},
|
|
|
|
|
dir_t: {},
|
|
|
|
|
dir_u: {},
|
2015-02-12 15:12:19 +03:00
|
|
|
},
|
|
|
|
|
file: 'this is a file',
|
|
|
|
|
}
|
2015-02-13 02:28:42 +03:00
|
|
|
// add some recursion for testing...
|
|
|
|
|
TREE.dir_d = TREE.dir_c.dir_b
|
|
|
|
|
TREE.dir_a.tree = TREE
|
2015-02-14 03:30:13 +03:00
|
|
|
TREE.dir_c.tree = TREE
|
2015-02-13 02:28:42 +03:00
|
|
|
TREE.dir_c.dir_b.tree = TREE
|
2015-02-12 15:12:19 +03:00
|
|
|
|
2015-02-12 19:07:13 +03:00
|
|
|
|
|
|
|
|
|
2015-03-16 20:40:08 +03:00
|
|
|
//---
|
|
|
|
|
|
2015-06-21 03:49:00 +03:00
|
|
|
var use_disabled = true
|
2015-06-21 04:15:54 +03:00
|
|
|
var show_files = false
|
2015-02-12 19:07:13 +03:00
|
|
|
|
2015-09-05 02:52:51 +03:00
|
|
|
requirejs(['../keyboard', '../../object', './browse'], function(k, o, br){
|
2015-03-16 20:40:08 +03:00
|
|
|
keyboard = k
|
|
|
|
|
object = o
|
2015-06-24 18:21:49 +03:00
|
|
|
browser = br
|
2015-02-12 19:07:13 +03:00
|
|
|
|
2015-06-21 18:46:46 +03:00
|
|
|
// Tree demo...
|
2015-06-21 16:12:20 +03:00
|
|
|
b = browser.Browser($('.container.tree'), {
|
2015-07-14 14:31:44 +03:00
|
|
|
path: '/dir_a/tree/dir_c/',
|
2015-06-21 18:46:46 +03:00
|
|
|
|
2015-06-21 03:49:00 +03:00
|
|
|
list: function(path, make){
|
2015-03-21 16:45:20 +03:00
|
|
|
var cur = TREE
|
2015-09-01 12:41:44 +03:00
|
|
|
this.path2list(path).forEach(function(p){
|
2015-03-21 16:45:20 +03:00
|
|
|
cur = cur[p]
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return Object.keys(cur)
|
2015-06-21 03:49:00 +03:00
|
|
|
// remove all strings...
|
2015-03-21 16:45:20 +03:00
|
|
|
.filter(function(k){
|
2015-06-21 04:15:54 +03:00
|
|
|
return show_files || typeof(cur[k]) != typeof('str')
|
2015-03-21 16:45:20 +03:00
|
|
|
})
|
2015-06-21 03:49:00 +03:00
|
|
|
.map(function(k){
|
|
|
|
|
// make the element...
|
2015-08-29 21:41:46 +03:00
|
|
|
var e = make(k, typeof(cur[k]) != typeof('str'))
|
2015-06-21 03:49:00 +03:00
|
|
|
// disable dir_b...
|
|
|
|
|
if(use_disabled && k == 'dir_b'){
|
|
|
|
|
e.addClass('disabled')
|
|
|
|
|
}
|
|
|
|
|
return k
|
|
|
|
|
})
|
2015-03-21 16:45:20 +03:00
|
|
|
},
|
2015-06-21 16:12:20 +03:00
|
|
|
open: function(path){
|
|
|
|
|
console.log('OPEN:', path)
|
|
|
|
|
},
|
2015-03-21 16:45:20 +03:00
|
|
|
})
|
|
|
|
|
.focus()
|
2015-06-21 16:12:20 +03:00
|
|
|
|
|
|
|
|
|
2015-06-22 18:09:37 +03:00
|
|
|
// Custom flat list demo...
|
2015-06-21 16:12:20 +03:00
|
|
|
f = browser.Browser($('.container.flat'), {
|
|
|
|
|
data: [
|
|
|
|
|
'option 1',
|
|
|
|
|
'option 2',
|
|
|
|
|
'option 3',
|
|
|
|
|
'option 4',
|
|
|
|
|
'option 5',
|
|
|
|
|
'option 6',
|
|
|
|
|
'option 7',
|
|
|
|
|
],
|
2015-06-26 03:07:58 +03:00
|
|
|
|
2015-07-14 02:02:37 +03:00
|
|
|
fullPathEdit: false,
|
2015-06-21 16:12:20 +03:00
|
|
|
traversable: false,
|
|
|
|
|
flat: true,
|
2015-06-20 20:51:43 +03:00
|
|
|
|
2015-06-21 16:12:20 +03:00
|
|
|
list: function(path, make){
|
|
|
|
|
return this.options.data
|
|
|
|
|
.map(function(k){
|
|
|
|
|
// make the element...
|
|
|
|
|
var e = make(k)
|
|
|
|
|
// disable dir_b...
|
|
|
|
|
if(use_disabled && k == 'option 4'){
|
|
|
|
|
e.addClass('disabled')
|
|
|
|
|
}
|
2015-06-22 17:22:01 +03:00
|
|
|
|
|
|
|
|
if(k == 'option 3'){
|
|
|
|
|
e.on('open', function(){ alert('openning: option 3!') })
|
|
|
|
|
}
|
2015-06-21 16:12:20 +03:00
|
|
|
return k
|
|
|
|
|
})
|
|
|
|
|
},
|
|
|
|
|
open: function(path){
|
|
|
|
|
console.log('OPEN:', path)
|
|
|
|
|
},
|
|
|
|
|
})
|
2015-06-22 17:22:01 +03:00
|
|
|
|
2015-06-22 18:09:37 +03:00
|
|
|
|
|
|
|
|
// Default flat list demo...
|
|
|
|
|
f2 = browser.makeList($('.container.flat2'), {
|
2015-06-22 17:22:01 +03:00
|
|
|
'option 1': function(_, p){ console.log('option:', p) },
|
|
|
|
|
'option 2': function(_, p){ console.log('option:', p) },
|
|
|
|
|
'option 3': function(_, p){ console.log('option:', p) },
|
|
|
|
|
'option 4': function(_, p){ console.log('option:', p) },
|
|
|
|
|
})
|
2015-07-12 18:51:16 +03:00
|
|
|
// another way to handle the opening of items...
|
|
|
|
|
.open(function(evt, text){
|
|
|
|
|
alert('>>> ' + text)
|
|
|
|
|
})
|
2015-08-29 21:41:46 +03:00
|
|
|
|
|
|
|
|
// path list demo...
|
|
|
|
|
f3 = browser.makePathList($('.container.pathlist'), {
|
2015-09-01 12:41:44 +03:00
|
|
|
// build a basic tree...
|
|
|
|
|
// XXX need a way to trigger open events with touch/mouse...
|
2015-08-29 21:41:46 +03:00
|
|
|
'/dir 1': function(_, p){ console.log('dir:', p) },
|
|
|
|
|
'dir 1/option 1': function(_, p){ console.log('option:', p) },
|
2015-09-05 16:07:08 +03:00
|
|
|
// add an element to two paths...
|
|
|
|
|
'dir 1|dir 2|dir 3/option 2/': function(_, p){ console.log('option:', p) },
|
2015-08-29 21:41:46 +03:00
|
|
|
'dir 2/option 3': function(_, p){ console.log('option:', p) },
|
|
|
|
|
'option 4': function(_, p){ console.log('option:', p) },
|
2015-08-31 20:13:29 +03:00
|
|
|
|
|
|
|
|
// XXX this is the wrong way to do this, but it shows a bug...
|
|
|
|
|
// XXX BUG: for some reason 2 and 3 are set to traversable while
|
|
|
|
|
// 1 is not...
|
2015-09-01 12:41:44 +03:00
|
|
|
'bug demo': function(_, p){
|
2015-08-31 20:13:29 +03:00
|
|
|
console.log('option:', p)
|
|
|
|
|
|
2015-09-01 12:41:44 +03:00
|
|
|
f3.update(p + '/', function(){ return [
|
|
|
|
|
'not traversable?!',
|
|
|
|
|
'traversable!',
|
|
|
|
|
'also traversable!'] })
|
2015-08-31 20:13:29 +03:00
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// render a custom sub-tree...
|
2015-09-05 16:07:08 +03:00
|
|
|
//'dynamic/*': function(path, make){
|
2015-08-31 20:13:29 +03:00
|
|
|
'dynamic/*': function(path, make){
|
2015-08-31 20:57:05 +03:00
|
|
|
console.log('listing:', path)
|
2015-08-31 20:13:29 +03:00
|
|
|
|
|
|
|
|
return [1,2,3]
|
|
|
|
|
.map(function(e){
|
|
|
|
|
make(e, true)
|
|
|
|
|
return e
|
|
|
|
|
})
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// this will override the 'dynamic/*' in case of longer
|
|
|
|
|
// matches...
|
2015-09-01 12:41:44 +03:00
|
|
|
// NOTE: this will make more sence when path patterns are
|
|
|
|
|
// supported...
|
2015-09-05 02:52:51 +03:00
|
|
|
'dynamic/1/*/*': function(path, make){
|
2015-08-31 20:13:29 +03:00
|
|
|
make('mooo!/')
|
|
|
|
|
make('fooo!')
|
|
|
|
|
},
|
2015-09-01 12:41:44 +03:00
|
|
|
|
|
|
|
|
// include another list...
|
|
|
|
|
// NOTE: we need to trim the path before passing it to the
|
|
|
|
|
// list to avoid confusing it...
|
|
|
|
|
// NOTE: this will lose any instance data and works here
|
|
|
|
|
// befause b references TREE in a closure...
|
|
|
|
|
// Thus, it is recommended to avoid storing state in
|
|
|
|
|
// the browser object.
|
|
|
|
|
// ...also note that internally nothing is stored in the
|
|
|
|
|
// browser object, other than the options.
|
|
|
|
|
'browser/*': function(path, make){
|
|
|
|
|
var list = b.options.list
|
|
|
|
|
|
|
|
|
|
// trim off the base path...
|
|
|
|
|
path = this.path2list(path).slice(1)
|
|
|
|
|
|
|
|
|
|
return list.call(this, path, make)
|
|
|
|
|
},
|
2015-08-29 21:41:46 +03:00
|
|
|
})
|
2015-02-12 19:07:13 +03:00
|
|
|
})
|
2015-02-12 15:12:19 +03:00
|
|
|
|
|
|
|
|
$(function(){
|
2015-03-21 16:45:20 +03:00
|
|
|
|
|
|
|
|
$('.container').first().remove()
|
2015-02-14 03:30:13 +03:00
|
|
|
|
2015-02-14 03:37:24 +03:00
|
|
|
$('.container').draggable({
|
|
|
|
|
cancel: ".path .dir, .list div"
|
|
|
|
|
})
|
2015-02-12 15:12:19 +03:00
|
|
|
})
|
|
|
|
|
|
2015-06-21 16:12:20 +03:00
|
|
|
|
|
|
|
|
|
2015-06-20 20:51:43 +03:00
|
|
|
var themeToggler = CSSClassToggler('body',
|
|
|
|
|
[
|
|
|
|
|
'none',
|
|
|
|
|
'light',
|
|
|
|
|
'dark',
|
|
|
|
|
],
|
|
|
|
|
function(state){
|
|
|
|
|
$('#theme').text(state)
|
|
|
|
|
})
|
2015-06-21 03:49:00 +03:00
|
|
|
function toggleDisabled(){
|
|
|
|
|
use_disabled = !use_disabled
|
|
|
|
|
|
2015-06-21 16:12:20 +03:00
|
|
|
use_disabled ? b.disableElements('_b') : b.enableElements('_b')
|
|
|
|
|
use_disabled ? f.disableElements('4') : f.enableElements('4')
|
2015-06-21 03:49:00 +03:00
|
|
|
}
|
2015-06-21 04:15:54 +03:00
|
|
|
function toggleFiles(){
|
|
|
|
|
show_files = !show_files
|
|
|
|
|
|
|
|
|
|
// NOTE: we need to update only because .list(..) does not show
|
|
|
|
|
// files at all and we need to re-render them, another way
|
|
|
|
|
// would be render everything but hide and show files via CSS
|
|
|
|
|
// class...
|
|
|
|
|
b.update()
|
|
|
|
|
}
|
2015-06-21 03:49:00 +03:00
|
|
|
|
2015-02-12 15:12:19 +03:00
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<body>
|
|
|
|
|
|
2015-06-20 14:50:54 +03:00
|
|
|
<button onclick="b.pop()"><</button>
|
|
|
|
|
<button onclick="b.push()">></button>
|
|
|
|
|
<button onclick="b.prev()">^</button>
|
|
|
|
|
<button onclick="b.next()">v</button>
|
2015-02-12 17:16:00 +03:00
|
|
|
|
2015-06-20 20:51:43 +03:00
|
|
|
<br>
|
|
|
|
|
<br>
|
|
|
|
|
|
|
|
|
|
Theme: <button id="theme" onclick="themeToggler()">none</button>
|
|
|
|
|
|
2015-06-21 03:49:00 +03:00
|
|
|
<br>
|
|
|
|
|
|
|
|
|
|
Disabled: <button id="theme" onclick="toggleDisabled()">toggle</button>
|
|
|
|
|
|
2015-06-21 04:15:54 +03:00
|
|
|
<br>
|
|
|
|
|
|
|
|
|
|
Files: <button id="theme" onclick="toggleFiles()">toggle</button>
|
|
|
|
|
|
2015-06-26 03:14:23 +03:00
|
|
|
<br>
|
|
|
|
|
<br>
|
|
|
|
|
Basic key bindings:
|
|
|
|
|
<ul>
|
2015-07-11 22:16:35 +03:00
|
|
|
<li><code>ctrl-a<code> - edit full path </li>
|
|
|
|
|
<li><code>ctrl-left<code> / <code>ctrl-Backspace<code> - go to root </li>
|
2015-06-26 03:14:23 +03:00
|
|
|
</ul>
|
2015-06-21 04:15:54 +03:00
|
|
|
|
2015-02-12 15:12:19 +03:00
|
|
|
|
|
|
|
|
<div class="container">
|
2015-09-07 18:51:42 +03:00
|
|
|
<div class="browse-widget">
|
2015-02-12 15:12:19 +03:00
|
|
|
<!-- title, optional -->
|
|
|
|
|
<div class="v-block title">
|
|
|
|
|
[title]
|
|
|
|
|
</div>
|
|
|
|
|
|
2015-06-18 00:13:30 +03:00
|
|
|
<!-- current path -->
|
2015-02-12 15:12:19 +03:00
|
|
|
<div class="v-block path">
|
|
|
|
|
<div class="dir">
|
|
|
|
|
[dir]
|
|
|
|
|
</div>
|
|
|
|
|
<div class="dir">
|
|
|
|
|
[dir]
|
|
|
|
|
</div>
|
|
|
|
|
<div class="dir">
|
|
|
|
|
[dir]
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
2015-06-18 00:13:30 +03:00
|
|
|
<!-- path list -->
|
2015-02-12 15:12:19 +03:00
|
|
|
<div class="v-block list">
|
|
|
|
|
<div>
|
|
|
|
|
[dir]
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
[dir]
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
[dir]
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- info, optional -->
|
|
|
|
|
<div class="v-block info">
|
|
|
|
|
[info]
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- buttons, optional -->
|
|
|
|
|
<div class="v-block actions">
|
|
|
|
|
[actions]
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
2015-06-21 16:12:20 +03:00
|
|
|
<div class="container tree">
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="container flat">
|
2015-03-21 16:45:20 +03:00
|
|
|
</div>
|
2015-02-12 15:12:19 +03:00
|
|
|
|
2015-06-22 17:22:01 +03:00
|
|
|
<div class="container flat2">
|
|
|
|
|
</div>
|
|
|
|
|
|
2015-08-29 21:41:46 +03:00
|
|
|
<div class="container pathlist">
|
|
|
|
|
</div>
|
|
|
|
|
|
2015-02-12 15:12:19 +03:00
|
|
|
</body>
|
|
|
|
|
</html>
|
2015-02-28 12:04:43 +03:00
|
|
|
<!-- vim:set ts=4 sw=4 : -->
|