Index: branches/testa/js/bookmarkmenu.js
===================================================================
--- branches/testa/js/bookmarkmenu.js	(revision 29)
+++ branches/testa/js/bookmarkmenu.js	(revision 29)
@@ -0,0 +1,92 @@
+class BookmarkMenu {
+    constructor(num, part) {
+	this.num = num
+	this.part = part
+	this.bookmark_op_sel = Globalx[part].bookmark_op_sel
+	this.bookmark_mgr = Globalx[part].bookmark_mgr
+	this.bookmark_sel = Globalx[part].bookmark_sel
+	this.key_sel = Globalx[part].key_sel
+	this.editor = Globalx[part].editor
+	this.textarea_sel = Globalx[part].textarea_sel
+	this.menu_sel = Globalx[part].menu_sel
+    }
+
+    add_bookmark() {
+	$( '#bookmark_displayname' ).val( Globalx[this.part].bookmark_mgr.get_restricted_display_name( Globalx[this.part].key ) )
+	$( '#bookmark_path' ).val( Globalx[this.part].key )
+	$( '#bookmarkDlg' ).dialog("open")
+    }
+
+    remove_bookmark() {
+	this.bookmark_mgr.remove( Globalx[this.part].key )
+	this.rebuild_bookmark_menu()
+    }
+
+    bookmark_op() {
+	if( $( this.bookmark_op_sel ).is(':checked') == true ){
+	    this.add_bookmark()
+	}
+	else{
+	    this.remove_bookmark()
+	}
+    }
+
+    set_click_handler() {
+	console.log( "set_click_handler() part=" + this.part + " num=" + this.num )
+	$( this.bookmark_op_sel ).on( 'click' , () => { this.bookmark_op() } )
+    }
+
+    register_bookmark_( path, displayname ) {
+	this.bookmark_mgr.add( path , displayname )
+	this.rebuild_bookmark_menu()
+    }
+
+    rebuild_bookmark_menu() {
+	$( this.bookmark_sel ).empty()
+
+	const array = this.bookmark_mgr.get_paths()
+
+	array.map( ( path ) => {
+	    const display_name = this.bookmark_mgr.display_name( path )
+	    content = `<li><a href="#" onclick="this.bookmark_action( '${this.part}' , '${this.path}' )">${display_name}</a></li>`;
+	    $( this.bookmark_sel ).append(content);
+	})
+    }
+
+    bookmark_action = ( part, key ) => {
+	if( key === null || key === "" ){
+	    alert("illeagal key=" + key )
+	    return
+	}
+	if(key === Globalx[part].key){
+	    return
+	}
+
+	let realData = Globalx.remotex.get_filelist( this.num )
+	Globalx[part].key = key
+	Globalx[part].key_indicate_file = false;
+
+	this.set_globalx_item_name( part, key )
+	$( this.key_sel ).val( key )
+
+	if( realData[Globalx[part].key].length == 0 ){
+	    Globalx[part].key_indicate_file = true;
+	    Globalx.remotex.get_content( this.num, this.part, Globalx[part].key , (content) => {
+		this.editor.getSession().setValue( content );
+		$( this.textarea_sel ).val( content );
+	    });
+	}
+	else{
+	    Globalx[part].key_indicate_file = false
+	    this.editor.getSession().setValue( "" );
+	    $( this.textarea_sel ).val( "" );
+	}
+	const bookmark_cb = $( this.bookmark_op_sel )
+	bookmark_cb.prop('checked', true)
+
+	$( this.menu_sel ).empty()
+
+	this.rebuild_bookmark_menu()
+	// this.make_menu_item_list(realData, Globalx[part].key)
+    }
+}
Index: branches/testa/js/remotex.js
===================================================================
--- branches/testa/js/remotex.js	(revision 29)
+++ branches/testa/js/remotex.js	(revision 29)
@@ -0,0 +1,73 @@
+//"php/content.php"
+class Remotex {
+    constructor( filename ){
+	this.filename = filename
+//	this.num = num
+//	this.part = part
+    }
+
+    make_url_params( arrayx ){
+	let params = new URLSearchParams();
+	arrayx.reduce(function(acc, element, index, array) {
+	    acc.append(element[0], element[1]);
+	    return acc;
+	}, params)
+	return params;
+    }
+
+    async get_filelist_from_remote( num, part, func ) {
+console.log("remotex get_filelist_from_remote num=" + num + " part=" + part )
+	let param_array = [['cmd', 'get_filelistx'], ['num', num ], ['part', part ]]
+	await this.fetchx( this.filename , param_array, func )
+    }
+
+    async update_filelist( num, part, func ) {
+console.log("remotex update_filelist num=" + num + " part=" + part )
+	let param_array = [['cmd', 'update_filelistx'], ['num', num ], ['part', part ]]
+	await this.fetchx( this.filename , param_array, func )
+    }
+
+    async get_content(num, part, path , func) {
+console.log("remotex get_content num=" + num + " part=" + part )
+	console.log("part=" + part)
+	let param_array = [['cmd', 'get_content'], ['num', num ], ['part', part ], ['path', path]]
+	await this.fetchx( this.filename , param_array, func )
+    }
+
+    async fetchx( filename, param_array, func) {
+	let params = this.make_url_params( param_array )
+	fetch(`${filename}?${params}`)
+	    .then((response) => response.text())
+	    .then((text) => { console.log(func) ; func(text)})
+	    .catch((error) => console.log(error));
+    }
+
+    handleDownload(e) {
+	let dl = document.getElementById("data-down-download")
+
+	if( Globalx[ e.data.part ].key_indicate_file === true ){
+	    this.get_content( e.data.num, e.data.part , Globalx[ e.data.part ].key , (content) => {
+		let blob = new Blob([ content ], { "type" : "text/plain" });
+
+		if (window.navigator.msSaveBlob) { 
+		    window.navigator.msSaveBlob(blob, Globalx[ e.data.part ].item_name); 
+		    
+		    // msSaveOrOpenBlobの場合はファイルを保存せずに開ける
+		    window.navigator.msSaveOrOpenBlob(blob, Globalx[ e.data.part ].item_name); 
+		} else {
+		    dl.download = Globalx[ e.data.part ].item_name
+		    dl.href = window.URL.createObjectURL(blob);
+		}
+	    })
+	}
+	else{
+	    e.preventDefault()
+	    dl.href = ""
+	    dl.download = ""
+	}
+    }
+}
+
+function handleDownload(e) {
+    Globalx.remotex.handleDownload(e)
+}
Index: branches/testa/js/sidemenu.js
===================================================================
--- branches/testa/js/sidemenu.js	(revision 29)
+++ branches/testa/js/sidemenu.js	(revision 29)
@@ -0,0 +1,176 @@
+class SideMenu {
+    constructor (num , part){
+	this.part = part
+	this.num = num
+	this.menu_sel = Globalx[part].menu_sel
+	this.menu_id = Globalx[part].menu_id
+	this.textarea_sel = Globalx[part].textarea_sel
+	this.bookmark_op_sel = Globalx[part].bookmark_op_sel
+    }
+    set_editor( editor ){
+	this.editor = editor /* Globalx[part].editor */
+    }
+
+    async setup(top_sel){
+	console.log("SideMenu setup menu_sel=" + this.menu_sel)
+	let content = `<ul id="${this.menu_id}"></ul>`
+	$(top_sel).append( content )
+	this.get_filelist_async( this.num , this.part , (text) => {
+	    console.log("SideMenu setup text=" + text)
+	    let str = text
+	    let obj
+	    if( str.match( /{.*}/ ) === null ){
+		obj = {}
+		console.log("sidemenu setup 1 num=" + this.num + " part=" + this.part)
+	    }
+	    else {
+		obj = JSON.parse( str )
+		console.log("sidemenu setup 2 num=" + this.num + " part=" + this.part + " str="+str)
+	    }
+	    console.log( "sidemenu setup this.num=" + this.num + " this.part=" + this.part )
+	    Globalx.storagex.change_cache( obj, this.num , this.part )
+	    
+	    this.make_menu_item_list( obj , Globalx[this.part].key)
+	    $( this.menu_sel ).menu({
+		classes: {
+		    "ui-menu": "highlight"
+		}
+	    })
+	})
+    }
+
+    async get_filelist_async( num , part , func){
+	if( part == "setting" ){
+	    console.log( "get_filelist_async START part="+ part)
+	}
+	let realData = Globalx.storagex.get_filelist( num , part )
+
+	if( part == "setting" ){
+	    console.log( "get_filelist_async 2 part=" + part)
+	}
+	if( realData === undefined || realData === null ){
+	    if( part == "setting" ){
+		console.log( "get_filelist_async 3 part=" + part)
+	    }
+	    await Globalx.remotex.get_filelist_from_remote( num, part, func )
+	    if( part == "setting" ){
+		console.log( "get_filelist_async 4 part=" + part)
+	    }
+	}
+	else{
+	    const obj = Globalx.storagex.get_filelist( num , part )
+	    this.make_menu_item_list( obj , Globalx[this.part].key)
+	    $( this.menu_sel ).menu({
+		classes: {
+		    "ui-menu": "highlight"
+		}
+	    })
+	}
+    }
+
+    menu_action( item_name , up_flag = false ) {
+	if( Globalx[this.part].key != '/' ){
+	    
+	}
+	let part = this.part
+	let jsondata = Globalx.storagex.get_filelist( Globalx.num , part)
+
+	Globalx[part].key_indicate_file = false;
+	if( up_flag ){
+	    this.set_globalx_item_name(part, "")
+	    Globalx[part].editor.getSession().setValue( "" );
+	    $( this.textarea_sel ).val( "" );
+	    if( Globalx[part].key != '/' ){
+		let array = Globalx[part].key.split("/")
+		array.pop()
+		if( array.length > 1 ){
+		    Globalx[part].key_type = false;
+		    this.set_globalx_item_name( part, array[array.length - 1] )
+
+		    Globalx[part].key = array.join('/')
+		    $( Globalx[part].menu_sel ).empty()
+		}
+		else {
+		    Globalx[part].key = '/'
+		    this.set_globalx_item_name( part, "" )
+		}
+	    }
+	}
+	else{
+	    if( Globalx[part].key == '/' ){
+		Globalx[part].key = Globalx[part].key + item_name
+	    }
+	    else{
+		Globalx[part].key = Globalx[part].key + '/' + item_name
+	    }
+	}
+	this.set_globalx_item_name(part, item_name)
+
+	const bookmark_cb = $( this.bookmark_op_sel )
+	if( Globalx[part].bookmarks.indexOf( Globalx[part].key ) >= 0 ){
+	    bookmark_cb.prop('checked', true)
+	}
+	else{
+	    bookmark_cb.prop('checked', false)
+	}
+
+	$( Globalx[part].key_sel ).val( Globalx[part].key )
+
+	$( this.menu_sel ).empty()
+
+	this.make_menu_item_list(jsondata, Globalx[part].key)
+
+	if( jsondata !== undefined && jsondata[Globalx[part].key] !== undefined ){
+	    if( jsondata[Globalx[part].key].length == 0 ){
+		Globalx[part].key_indicate_file = true;
+		Globalx.remotex.get_content( this.num, this.part, Globalx[part].key , (content) => {
+		    //console.log("content=" + content)
+		    Globalx[part].editor.getSession().setValue( content );
+		    $( Globalx[part].textarea_sel ).val( content );
+		});
+	    }
+	}
+    }
+
+    make_menu_item_list( realData , key){
+	let content = null
+	let ary = realData[ key ]
+	
+	let name = '..(Up)'
+	content = `<li class="ui-menu-item"><div class="ui-menu-item-wrapper hasmenu" onclick="menu_action( '${this.part}', '${this.name}', true)">${name}</div></li>`;
+	//	content = `<li class="ui-menu-item"><div class="ui-menu-item-wrapper hasmenu" onclick="this.menu_action_2">${name}</div></li>`;
+	$( this.menu_sel ).append(content);
+
+	if( ary !== undefined ){
+	    ary.map( ( item_name ) => {
+		content = `<li class="ui-menu-item"><div class="ui-menu-item-wrapper hasmenu" onclick="menu_action( '${this.part}', '${item_name}' , false)">${item_name}</div></li>`;
+		//	    content = `<li class="ui-menu-item"><div class="ui-menu-item-wrapper hasmenu" onclick="this.menu_action_2">${item_name}</div></li>`;
+		$( this.menu_sel ).append(content);
+	    })
+	}
+    }
+
+    set_globalx_item_name( item_name ) {
+	Globalx[this.part].item_name = item_name
+	let dl = $( '#download' )
+	dl.attr('download' , Globalx[this.part].item_name)
+	dl.removeAttr('href')
+    }
+
+    contextmenux0( menu_sel ) {
+	$( menu_sel ).contextmenu({
+	    delegate: ".hasmenu",
+	    menu: [
+		{title: "Copy", cmd: "copy", uiIcon: "ui-icon-copy"},
+		{title: "----"},
+		{title: "More", childern: [
+		    {title: "Sub 1", cmd: "sub1"},
+		    {title: "Sub 2", cmd: "sub1"}
+		]}
+	    ],
+	    select: (event, ui) => {
+		alert("select " + ui.cmd + " on " + ui.target.text());
+	    }
+	});
+    }
+}
Index: branches/testa/js/storagex.js
===================================================================
--- branches/testa/js/storagex.js	(revision 29)
+++ branches/testa/js/storagex.js	(revision 29)
@@ -0,0 +1,196 @@
+class Storagex {
+    constructor () {
+	this._globalStorage = {}
+	this._items = ["num", "index", "parts"]
+    }
+
+    ensure_data_structure ( item , parts ) {
+	parts.map( (part) => {
+	    if( item[part] === undefined ){
+		item[part] = {}
+	    }
+	} )
+    }
+
+    save_as_info_from_globalx() {
+	this.save_as_info_from_globalx_with_items(this._items)
+    }
+
+    save_as_info_from_globalx_with_items(items){
+	let info = {}
+
+	this.copy_object( info, Globalx, items )
+	Globalx.parts.map( (part) => {
+	    this.save_as_info_from_globalx_part(part , info)
+	} )
+
+	this.save_as_info_to_local_storage(info)
+    }
+
+    save_as_info_to_local_storage(obj){
+	this.save_to_local_storage("info" , obj)
+    }
+
+    save_as_info_from_globalx_part(part , info){
+	info[part] = {}
+	this.copy_obj_with_part(info, Globalx, part)
+	
+	info[part].bookmark_mgr = this.convert_to_plain_object_from_bookmark_mgr(Globalx[part].bookmark_mgr)
+    }
+
+    save_to_local_storage(key , obj) {
+	this._globalStorage[key] = obj
+	localStorage.setItem(key , JSON.stringify(obj) )
+    }
+
+    change_cache( obj , num , part ){
+	const key = `${num}/${part}`
+	this._globalStorage[key] = obj
+	localStorage.setItem(key , JSON.stringify(this._globalStorage[key]) )
+    }
+
+    convert_to_array_from_bookmark_mgr(mgr){
+	return {
+	    paths: mgr.get_paths(),
+	    items: mgr_to_array(mgr),
+	    max_display_name_length: mgr.max_display_name_length()
+	}
+    }
+
+    convert_to_plain_object_from_bookmark_mgr(mgr){
+	const paths = mgr.get_paths()
+	const new_bk_items = paths.map( ( path ) => {
+	    let ret = {
+		path: "",
+		display_name: ""
+	    }
+	    item = mgr.get(path)
+	    if( item != null ){
+		ret.path = path
+		ret.display_name = item.display_name
+	    }
+	    return ret
+	} )
+	
+	return {
+	    max_display_name_length: mgr.max_display_name_length,
+	    items: new_bk_items
+	}
+    }
+
+    copy_obj_with_part(to_obj, from_obj, part){
+	const items = ["editor_id", "textarea_sel" , "menu_id", "bookmark_id", "bookmark_op_id", "item_name", "bookmarks", "key_sel", "download_sel", "menu_sel", "bookmark_sel", "bookmark_op_sel", "bookmark_mgr" ]
+	const init_value_items = ["editor", "key_indicate_file", "key"]
+	
+	if( to_obj[part] === undefined ){
+	    to_obj[part] = {}
+	}
+	this.copy_object( to_obj[part], from_obj[part], items )
+	this.copy_object( to_obj[part], GlobalxInitValue, init_value_items )
+    }
+
+    copy_object( to_obj, from_obj, items ){
+	let x = null
+	items.map( (item) => {
+	    x = from_obj[item]
+	    to_obj[item] = x
+	} )
+    }
+
+    restore_info(){
+	const item_name = "info"
+	return this.restore_from_localstorage(item_name)
+    }
+
+    restore_from_localstorage(num, part= null){
+	let key = null
+	let ret = null
+	if( part === null ){
+	    key = num
+	}
+	else{
+	    key = `${num}/${part}`
+	}
+	const str = localStorage.getItem(key)
+	if( str ){
+	    ret = this._globalStorage[key] = JSON.parse(str)
+//	    console.log("rfl 1 key=" + key + " str=" + str)
+	}
+	else{
+	    if( this._globalStorage[key] !== undefined ){
+		delete this._globalStorage[key]
+	    }
+	    ret = this._globalStorage[key]
+	    console.log("rfl 2 key=" + key + " undefined")
+	}
+
+	return ret
+    }
+
+    restore_globalx_from_info(){
+	let need_to_save = false
+	const info = this.restore_info()
+	if( info !== undefined && info !== null ){
+	    this.restore_info_to_globalx_with_items(info, this._items)
+	}
+	else{
+	    need_to_save = true
+	}
+
+	return need_to_save
+    }
+
+    convert_to_bookmark_mgr_from_plain_object(mgr) {
+	let new_mgr = new BookmarkMgr( mgr.max_display_name_length )
+	
+	const bk_items = mgr.items
+	bk_items.map( (bk_item) => {
+	    return new_mgr.add( bk_item.path , bk_item.display_name)
+	})
+	return new_mgr
+    }
+
+    restore_info_to_globalx_with_items(info, items){
+	this.copy_object( Globalx, info, items )
+	
+	info.parts.map( (part) => {
+	    this.restore_globalx_from_info_part(info , part)
+	} )
+    }
+
+    restore_globalx_from_info_part(info , part){
+	info[part].bookmark_mgr = this.convert_to_bookmark_mgr_from_plain_object(info[part].bookmark_mgr)
+	this.copy_obj_with_part(Globalx, info, part)
+    }
+
+    get_filelist(num, part) {
+	return this.restore_from_localstorage(num, part)
+    }
+
+
+
+    reset_info = () => {
+	let item_name = 'info'
+	reset_localstorage(item_name)
+    }
+    
+    reset_localstorage(item_name){
+	delete this._globalStorage[item_name]
+	localStorage.removeItem(item_name)
+    }
+
+    clear_localstorage = () => {
+	let keys = Object.keys(this._globalStorage)
+	keys.map( (v) => { delete this._globalStorage[v] } )
+	
+	localStorage.clear()
+    }
+    show_localstorage() {
+	window.open('local_storage.html', '_blank')
+    }
+
+    show_globalx() {
+	localStorage.setItem('globalx' , _globalStorage[0] )
+    }
+}
+
