2016年8月5日金曜日

ファイルにタグを付けて管理するソフトを改良した

先日公開したtags.wsfを数日間、自分で使いこんでみて、使い勝手を良くするためのアイデアがいくつか浮かんだので、それを反映したものを作成しました。
tags.wsfはドラッグ&ドロップで使うことを基本としていますが、今回作成したものはキーボードでの操作を前提とした設計になっていて、tags.wsfのバージョンUP版という位置づけではないのでtags.wsfのソースは変更していません。




起動画面



tags.wsfではファイルもフォルダも「アイテム」としてタグ付けできる扱いにしていましたが、今回の「tags.hta」ではタグ付けできるのはファイルのみです。


まずは「フォルダPath」の入力欄に任意のPathをコピペします。



フォルダPath入力欄でエンターキーを押すか、「list」ボタンを押すと「ファイル」のリストが更新されます。



任意のファイルを選択して、エンターキーを押すか「open」ボタンを押すと、「開き方」リストで選択中の開き方に従って、選択中のファイルを開きます。
今回は「explorer」が選択中だったので、以下のように対象ファイルが選択された状態の新しいエクスプローラが開きました。



ちなみに「開き方」の選択肢は、「tags.hta」を起動すると自動作成される「開き方」というフォルダの中のファイルが入っています。ファイルを追加したり編集すれば開き方を変更することが可能です。



「タグ」の入力欄をフォーカス状態にすると、画面下の広いエリアが2つに分かれます。
上がタグ選択用エリアです。



タグを何も登録していない状況ではタグ選択用エリアは真っ白のままで何も表示されません。
まずは「タグ」の入力欄にカンマ区切りで以下のように複数のタグを入力します。



ファイルにつけたいタグを記入し終えたら、エンターキーを押すか「chng」ボタンを押すと、選択中のファイルとタグが紐づけられます。

紐づけられた情報がある時は「タグ」の入力欄をフォーカス状態にすると、タグ選択用エリアに登録済みタグが表示されます。



「タグ選択用エリア」にタグが表示されている時にカーソルキーで「↑」「↓」を押すと、タグを選択できます。(選択中のタグは背景色が付きます)



タグを選択した状態で「カンマ」キーを入力すると、選択中のタグが「タグ」入力欄に追記されます。



タグ入力欄に何か文字列を入力すると、タグの選択用リストに表示されるタグは、入力された文字を含むタグだけに絞り込まれます。
上記のように「hta」「sample」のタグが登録済みの時は「e」と入力すると以下のようになります。



タグ入力欄に、カンマ区切りで複数のタグを入力している時は、一番右にあるカンマから末尾までの文字列でタグが絞り込まれます。つまり、タグ入力欄に「sample,ta」と入力すると、末尾の「ta」で絞り込まれたタグが以下のように表示されます。



画面最下部のエリアには、タグ入力欄に入力したタグを全て含むファイルだけが絞り込み表示されます。(上記の「sample,ta」と入力されている状態では「sample」と「ta」というタグが付いているファイルを検索していて「ta」と「hta」は一致しないため、表示されていません)



検索タグに対応するファイルとして表示されたエリアでは、登録済みファイルのファイルPathを変更できます。
※ただし、変更後のPathに対応するファイルが見つからない場合は変更は反映されません。



ファイルPathを変更してエンターキーを押すか、入力欄からフォーカスを外すと、変更後のPathに対応するファイルの有無がチェックされます。
ファイルが見つからないと以下のメッセージが表示されます。



登録済みファイルのファイルPathを削除すると



以下のメッセージが表示されます。



「OK」を押すと、対象ファイルの登録が解除されて、表示されなくなります。




以下、ソースです。
<html>
<head>
<title>tags</title>
<style type="text/css">
.max{width:100%;height:100%;}
.w-max{width :100%;}
.h-max{height:100%;}
.bc{border-collapse:collapse;}
.no_mp{margin:0px;padding:0px;}
.nowrap{white-space:nowrap;}
.of-auto{overflow:auto;}
.ta-c{text-align:center;}
button{font-family:MS ゴシック;}
</style>
</head>
<body style="overflow:hidden;">
<table class="max bc">
<tr height=1>
<td class=no_mp>
<table class="w-max bc">
<tr>
<td width=1><span class=nowrap>フォルダPath</span></td>
<td><input id=inpPath class=w-max></td>
<td width=1 class=ta-c><button id=btnDIR>list</button></td>
</tr>
<tr>
<td width=1><span class=nowrap>ファイル</span></td>
<td><select id=selファイル class=w-max></select></td>
<td width=1><button id=btnOpen>open</button></td>
</tr>
<tr>
<td width=1><span class=nowrap>タグ</span></td>
<td><input id=inpタグ class=w-max></td>
<td width=1><button id=btnTag>chng</button></td>
</tr>
<tr>
<td width=1><span class=nowrap>開き方</span></td>
<td><select id=sel開き方 class=w-max></select></td>
<td width=1><button id=btnCode>code</button></td>
</tr>
</table>
</td>
</tr>
<tr><td><div id=divタグ class="max of-auto" style="border:solid 1px #000;"></div></td></tr>
<tr>
<td>
<div class="max of-auto" style="border:solid 1px #000;">
<table class="w-max bc" id=tbl>
<tr style="display:none">
<td style="padding:5px;"><span style="display:none;"></span><input class=w-max><input class=w-max></td>
<td width=1 valign="middle"><button style="height:50px;">open</button></td>
</tr>
</table>
</div>
</td>
</tr>
</table>
</body>
<script>
onload = function(){
使用前準備()
var ops=sel開き方.options, arr=getFileList(path開き方)
for(var i=0,L=arr.length;i<L;i++){ ops[i] = new Option(arr[i] , Read(path開き方+'/'+arr[i])) }
}
// Enterキーの判定も多いので
isEnter=function(fun){return event.keyCode==13 ? (fun ? fun() : true) : false}
使用前準備=function(){
基準フォルダ = fs.GetParentFolderName(location.href.replace(/^file:\/\/\/([A-Z]:)/,'$1').replace(/^file:/,''))
pathタグ = 基準フォルダ+'/タグ.txt'
pathアイテム = 基準フォルダ+'/アイテム.txt'
path紐づけ = 基準フォルダ+'/紐づけ'
path紐づけ_tag2item = path紐づけ+'/tag2item'
path紐づけ_item2tag = path紐づけ+'/item2tag'
path開き方 = 基準フォルダ+'/開き方'
var makeDir=function(p){return fs.FolderExists(p) ? 0 : fs.CreateFolder(p)}
makeDir(path紐づけ )
makeDir(path紐づけ_tag2item)
makeDir(path紐づけ_item2tag)
if(makeDir(path開き方)){
Write(path開き方+'/explorer' , function(アイテム名){ shell.run('explorer /select,'+getコマンドライン用path(fs.GetAbsolutePathName(アイテム名))) })
Write(path開き方+'/関連付け済みprgで開く', function(アイテム名){ shell.run(getコマンドライン用path(アイテム名)) })
}
// 各関数から利用するファイルをあらかじめ読み込んでおく
全体={
arrアイテム:RAS(pathアイテム),
arrタグ :RAS(pathタグ)
}
全体.objアイテム = arr2obj(全体.arrアイテム)
全体.objタグ = arr2obj(全体.arrタグ )
全体.arrタグ_sorted = 全体.arrタグ.slice(0, 全体.arrタグ.length).sort()
}
inpPath.onfocus =function(){ this.select() }
inpPath.onkeydown=function(){ isEnter(ファイルリスト更新) }
btnDIR.onclick =function(){ ファイルリスト更新() }
ファイルリスト更新=function(){
var path=inpPath.value.replace(/\\/g,'/'), arr=getFileList(path), ops=selファイル.options
path = path + (path.match(/\/$/) ? '' : '/')
for0L(arr, function(i, ファイル名){ ops[i] = new Option(path+ファイル名) })
selファイル.onchange()
selファイル.focus()
}
selファイル.onchange=function(){
var アイテム名=selOps(this).text, アイテムCD=全体.objアイテム[アイテム名]
inpタグ.value = isFinite(アイテムCD) ? code2name(RAS(path紐づけ_item2tag+'/'+アイテムCD), 全体.arrタグ) : ''
検索()
}
selファイル.onkeydown=function(){ isEnter(btnOpen.onclick) }
btnOpen.onclick=function(){ selファイル[sI]==-1 ? alert('ファイルが選択されていません') : fun開く(selOps(selファイル).text) }
fun開く=function(アイテム名){
// 「開き方」で選択中の開き方で、選択中のファイルを開く。
var v=selOps(sel開き方).value, fun
eval('fun = '+v)
fun(アイテム名)
inpタグ.focus()
}
inpタグ.onfocus=function(){ this.onkeyup() }
inpタグ.onkeyup=function(){ タグ選択肢の表示を更新() }
タグ選択肢の表示を更新=function(){
if(タグ選択をキーで切り替え.sw実行){ return タグ選択をキーで切り替え.sw実行=false }
var arr=inpタグ.value.split(','), 最後=arr[arr.length-1]
divタグ[iH] = '<table class=w-max><tr onmousemove="タグonmousemove(this)" onclick="タグ選択(this[FI][iT])"><td></td><td width=1><button onclick="var tr=this[PA][PA];タグ削除(tr, tr[FI][iT])">del</button></td></tr></table>'
var tbo=divタグ[FI][FI], trModel=tbo[FI], newTR
for0L(全体.arrタグ_sorted, function(i, タグ名){
// カンマ区切りで分けて最後の文字列だけに注目して、その文字列を含むタグだけをinsertする
if(最後.length && タグ名.indexOf(最後)==-1){ return }
tbo.insertBefore(newTR = trModel.cloneNode(true))
newTR[FI][iT] = タグ名
})
trModel.removeNode(true)
検索()
// 検索するとdivタグの中身が更新される。
// 検索前に着色()が実行されていた場合、着色.tdの中身は古い状態でのtdを参照している。
// 古いtdへの参照を残したままinpタグ.onblurイベントが発生すると着色.td.onclickが実行されてしまい
// inpタグ.valueの中身をカンマ区切りにした末尾の文字列が古いtdの[iT]で上書きされてしまう。(td自体はremove済みのせいか、iTは長さ0の文字列になっている)
// そうならないように、検索語は着色.tdの中身をundefinedにしておく。
// タグ選択をキーで切り替え.選択中も古いtrsに対する行番号を記録しているのでundefinedにしておく。
着色.tr = 着色.i = undefined
}
タグonmousemove=function(tr){
// タグ選択用エリアのtdエレメントの上にマウスカーソルが乗ると呼ばれる。
document.body.onmousemove = function(){
// trエレメントの外にマウスカーソルが移動したら選択状態を解除する。
this.onmousemove = null
着色()
}
着色(tr)
cancelBubble()
}
着色=function(tr){
// タグ選択用エリアでどのタグを選択中なのか視覚的に分かるように、tdエレメントの背景色を付けたり消したりする。
var m=me()
if(m.tr){m.tr.style.backgroundColor = ''}
m.tr = tr
if(!tr){
m.i = undefined
return
}
m.tr.style.backgroundColor = '#ccf'
setScrollTop(tr)
for(var i=0,tr_=tr; tr_[PR] ;tr_=tr_[PR], i++){}
m.i = i
}
タグ選択=function(タグ名){
// タグ選択用エリアのtdエレメントをクリックするか、タグ入力欄で「,」を入力したりすると呼ばれる。
// タグ入力欄の末尾に選択されたタグを追記する。
var arr=inpタグ.value.split(','), L=arr.length
arr[L-1] = タグ名
inpタグ.value = '' // カーソル位置を末尾にするために一旦カラにする
inpタグ.focus()
inpタグ.value = arr
}
タグ削除=function(エレメント, タグ名){
// これを呼び出したボタンの上位エレメントのonclickが実行されないようにcancelBubbleしておく。
cancelBubble()
if(!confirm('['+タグ名+']のタグを削除しますか?')){ return }
エレメント.removeNode(true)
着色()
登録解除(タグ名, true)
検索()
}
inpタグ.onkeydown=function(){
document.body.onmousemove = null
switch(event.keyCode){
case 13 : return btnTag.onclick()
case 38 : return タグ選択をキーで切り替え('up')
case 40 : return タグ選択をキーで切り替え('down')
case 188: // カンマ
var tr=divタグ[FI][FI][CN][着色.i]
if(!tr){return}
var t=this.value
タグ選択(tr[FI])
return
// default : document.title = event.keyCode
}
}
タグ選択をキーで切り替え=function(方向){
var trs=divタグ[FI][FI][CN], m=me(), 選択中=着色.i, 末端=trs.length-1
// この関数を呼び出すkeydownイベントの後にはkeyupイベントが発生する。
// keyupイベント発生時には検索機能でdivタグの中身が書き換えられる。
// この関数の動作直後は書き換えを阻止したい。
m.sw実行 = true
// 選択肢が一つもない場合は選択中=undefinedにする。
if(trs.length==0){ return 着色() }
// 何も選択していない状況ならup=末端、down=0にする。
if(!isFinite(選択中)){ return 着色(trs[方向=='up' ? 末端 : 0]) }
// 先頭か終端のどちらかを選択している時に選択肢のない側へ移動指示が出たら選択中=undefinedにする。
if(選択中==0 && 方向=='up' ){ return 着色() }
if(選択中==末端 && 方向=='down'){ return 着色() }
// 上記のどれでもない場合はupならデクリメント、downならインクリメントする
着色(trs[選択中 + (方向=='up' ? -1 : 1)])
}
検索=function(){
// タグ入力欄の内容に対応するファイルを絞り込んで、ファイルPathやファイルに付いているタグなどを表示する。
var t=inpタグ.value, obj対象アイテムCD={}
for0L(t?t.replace(/,+/g,',').replace(/,$/,'').split(','):[], function(i, タグ名){
var タグCD = 全体.objタグ[タグ名]
var arr紐づけ = isFinite(タグCD) ? RAS(path紐づけ_tag2item+'/'+タグCD) : []
if(i==0){
// 一周目は無条件で対象アイテムCDに追加していく。
for0L(arr紐づけ, function(i, CD){ obj対象アイテムCD[CD] = true })
}else{
// 二周目以降はAND検索でtrueになったアイテムCDだけ残していく。
var obj = {}
for0L(arr紐づけ, function(i, CD){ if(obj対象アイテムCD[CD]){ obj[CD]=true } })
obj対象アイテムCD = obj
}
})
var tbo=tbl[FI], trs=tbo[CN], trModel=trs[0], newTR
while(trs[1]){trs[1].removeNode(true)}
for(CD in obj対象アイテムCD){
var アイテム名 = 全体.arrアイテム[CD-0]
tbo.insertBefore( newTR=trModel.cloneNode(true) )
newTR.style.display = 'block'
var elems=newTR[CN][0][CN] // SPAN, INPUT(アイテム名), INPUT(arrタグ名)
elems[0][iT] = アイテム名
elems[1].value = アイテム名
elems[2].value = code2name(RAS(path紐づけ_item2tag+'/'+全体.objアイテム[アイテム名]), 全体.arrタグ)
elems[1].onchange = function(){ アイテム名変更(this[PR][iT] , this ) }
elems[2].onchange = function(){ タグ付け(this[PR].value, this.value) }
elems[1].onkeydown = function(){ isEnter() ? this.onchange() : 0 }
elems[2].onkeydown = function(){ isEnter() ? this.onchange() : 0 }
newTR[CN][1][FI].onclick = setProp(function(){ fun開く(me().name) }, {name:アイテム名})
}
}
btnTag.onclick=function(){
if(selファイル[sI]==-1){return alert('ファイルが選択されていません') }
タグ付け(selOps(selファイル).text, inpタグ.value)
selファイル.focus()
}
btnCode.onclick=function(){ shell.run('notepad '+getコマンドライン用path(path開き方+'/'+selOps(sel開き方).text)) }
アイテム名変更=function(before, inp){
var after=inp.value
var 中断=function(msg){ msg?alert(msg):0;inp.value = before }
// afterの名前が登録済みなら中断する
if(全体.objアイテム[after]){ return 中断('['+after+']は登録済みのため、この名前に変更することはできません') }
// afterが長さ0の文字列なら登録解除するのか確認する
if(!after){
if(!confirm('['+before+']の登録を解除しますか?')){ return 中断() }
inp[PA][PA].removeNode(true)
return 登録解除(before, false)
}
if(!fs.FileExists(after)){ return 中断('指定されたPathに対応するファイルが見つかりません') }
// 変更処理を実行する。
全体.objアイテム[after] = 全体.objアイテム[before]
全体.arrアイテム[全体.objアイテム[after]] = after
delete 全体.objアイテム[before]
Write(pathアイテム, 全体.arrアイテム.join('\r\n'))
}
登録解除 = function(名前, swタグ){
// タグ(もしくはファイル)の名前を選択肢から削除する。
var obj全体, arr全体, path, path逆, pathNames
if(swタグ){
// 登録を解除するのがタグの場合
obj全体 = 全体.objタグ
arr全体 = 全体.arrタグ
path = path紐づけ_tag2item
path逆 = path紐づけ_item2tag
pathNames = pathタグ
}else{
obj全体 = 全体.objアイテム
arr全体 = 全体.arrアイテム
path = path紐づけ_item2tag
path逆 = path紐づけ_tag2item
pathNames = pathアイテム
}
var CD=obj全体[名前], 末尾CD=arr全体.length-1, 末尾名=arr全体[末尾CD]
delete obj全体[名前]
// ファイルは中身を読み込んで逆引き方向を解除してから削除する
var arr逆CD=RAS(path+'/'+CD), 逆CD, arr, newArr
for0L(arr逆CD, function(i, 逆CD){
ファイルからCD削除(path逆+'/'+逆CD, [CD])
})
fs.DeleteFile(path+'/'+CD)
// 削除対象の名前が使用していたCDを、別の名前で利用してCDを節約する
if(CD != 末尾CD){
// 末尾CDの逆引きを修正して、新しいCDの名前で保存する。
arr=RAS(path+'/'+末尾CD)
for0L(arr, function(i,逆CD){
var path_=path逆+'/'+逆CD, arr逆=RAS(path_)
for0L(arr逆, function(i,v){ arr逆[i] = (v==末尾CD ? CD : v) })
Write(path_, arr逆.join('\r\n'))
})
Write(path+'/'+CD, arr.join('\r\n'))
obj全体[末尾名] = CD
arr全体[CD ] = 末尾名
fs.DeleteFile(path+'/'+末尾CD)
}
arr全体.pop()
Write(pathNames, arr全体.join('\r\n'))
if(swタグ){ 全体.arrタグ_sorted = arr全体.slice(0, arr全体.length).sort() }
}
ファイルからCD削除=function(path, arr削除するCD){
// 指定されたPathのファイル内から削除対象CDを取り除いて上書きする。
var arr=RAS(path), newArr=[], obj=arr2obj(arr削除するCD)
for0L(arr, function(i, CD){ isFinite(obj[CD]) ? 0 : newArr.push(CD) })
Write(path, newArr.join('\r\n'))
}
タグ付け=function(アイテム名, txtタグ){
var arrタグ_選択=txtタグ.split(',')
// 新規要素がある場合、ファイルを更新する。
追加([アイテム名], 全体.objアイテム, 'アイテム')
追加(arrタグ_選択, 全体.objタグ , 'タグ' )
タグ選択肢の表示を更新()
// タグとアイテムの紐づけを更新する。
紐づけ更新(アイテム名, arrタグ_選択)
// 紐づけ変更後の表示に更新する。
検索()
}
getコマンドライン用path = function(path){var s=path.indexOf(' ')?'"':''; return s+path+s}
arr2obj = function(arr){
// 配列になっている値をオブジェクトのプロパティ名にして、そのオブジェクトを返す。
var obj={}
for0L(arr, function(i,v){obj[v] = i})
return obj
}
追加=function(arr追加要素, obj全体, 種類){
var arr新規=get新規要素の配列(obj全体, arr追加要素), arr全体
if(!arr新規.length){return}
arr全体 = 全体['arr'+種類].concat( arr新規 )
全体['arr'+種類] = arr全体
全体['obj'+種類] = arr2obj(arr全体)
if(種類=='タグ'){ 全体.arrタグ_sorted = arr全体.slice(0, arr全体.length).sort() }
Write(種類=='タグ'?pathタグ:pathアイテム, arr全体.join('\r\n'))
}
get新規要素の配列=function(obj元, arr新){
var arr=[]
for0L(arr新, function(i,v){ obj元[v]==undefined ? arr.push(v) : 0 })
return arr
}
紐づけ更新=function(アイテム名, arrタグ名){
// アイテムに紐づけるタグの増減をファイルに反映する。
var アイテムCD=全体.objアイテム[アイテム名], path=path紐づけ_item2tag+'/'+アイテムCD, arrタグCD=name2code(arrタグ名, 全体.objタグ)
// 更新前のファイルを読み込む。
var arr元タグCD = RAS(path)
// 更新によって紐づけが解除されるタグCDのリストを得る。第2引数にしかない要素の配列が入る。
var arr削除CD = get新規要素の配列(arr2obj(arrタグCD), arr元タグCD)
// 紐づけ解除されるタグからアイテムへの紐づけを解除する。
for0L(arr削除CD, function(i, タグCD){ ファイルからCD削除(path紐づけ_tag2item+'/'+タグCD, [アイテムCD]) })
// 逆に増える要素のリストを得る。
var arr追加CD = get新規要素の配列(arr2obj(arr元タグCD), arrタグCD)
// 紐づけ追加されるタグにアイテムCDを追記する。
for0L(arr追加CD, function(i, タグCD){
var path=path紐づけ_tag2item+'/'+タグCD, arr=RAS(path)
arr.push(アイテムCD)
Write(path, arr.join('\r\n'))
})
// アイテムCDのファイルを更新する
Write(path紐づけ_item2tag+'/'+アイテムCD, arrタグCD.join('\r\n'))
}
code2name = function(arrCD, arrAllName){
for(var arr=[],i=0,L=arrCD.length;i<L;i++){ arr[i]=arrAllName[arrCD[i]] }
return arr
}
name2code = function(arrName, obj){
for(var arr=[],i=0,L=arrName.length;i<L;i++){ arr[i]=obj[arrName[i]] }
return arr
}
del要素=function(arr, val){
for(var i=0,L=arr.length;i<L;i++){ if(arr[i]==val){ return arr.slice(0,i).concat(arr.slice(i+1,L)) } }
}
// 以下、定型文
fs = new ActiveXObject('Scripting.FileSystemObject')
shell = new ActiveXObject('WScript.Shell')
Read = function(path){var r=fs.OpenTextFile(path,1,true), v=r.AtEndOfStream?'':r.ReadAll(); r.Close(); return v}
RAS = function(path){var str=Read(path); return str.length ? str.split('\r\n') : []}
Write = function(path,str){var w=fs.CreateTextFile(path); w.Write(str); w.Close()}
gsf2 = fs.GetSpecialFolder(2)
iT='innerText', iH='innerHTML', FI='firstChild', NE='nextSibling', PR='previousSibling', PA='parentNode', CN='childNodes', sI='selectedIndex'
;(function(){
// dirコマンドによるファイル(フォルダ)リスト取得
var funCore=function(path, swDir){
var pathTMP=gsf2+'/'+fs.getTempName()+'.txt'
shell.run('cmd /C dir /A'+(swDir?'':'-')+'D /B "'+path+'" > "'+pathTMP+'"', 0, true)
var arr=RAS(pathTMP)
arr.pop()
fs.deleteFile(pathTMP)
return arr
}
getFileList=function(path){ return funCore(path) }
getDirList =function(path){ return funCore(path,true) }
})();
selOps=function(sel){return sel.options[sel[sI]]}
me=function(){return arguments.callee.caller}
cancelBubble=function(){event.cancelBubble = true}
for0L=function(arr, fun){for(var i=0,L=arr.length; i<L ;i++){ fun(i, arr[i]) }}
setProp=function(対象, obj, swサブ){
var getNames=function(obj){var arr=[],name,i=0; for(name in obj){arr[i++] = name}; return arr}
setProp=function(対象, obj, swサブ){
var arr=getNames(obj)
// サブプロパティは無視してobjの最上位プロパティを対象にコピーする場合は、以下の1行で終了。
if(!swサブ){ for0L(arr, function(i,v){ 対象[v] = obj[v] }); return 対象 }
// サブプロパティを列挙して、列挙するものがない部分だけコピーする場合は
// プロパティの数だけ再帰的実行する。←循環参照があると無限ループしてしまうので注意。
for0L(arr, function(i,v){
var arr=getNames(obj[v])
if(arr.length){
if(!対象[v]){ 対象[v] = {} }
setProp(対象[v], obj[v], true)
}else{
// 子要素が
対象[v] = obj[v]
}
})
return 対象
}
return setProp(対象, obj, swサブ)
}
// スクロール表示するエリア内の任意のアイテムが見える位置までスクロールさせるための関数
;(function(){
var スクロールエリア = divタグ
var getOffsetTop = function(elem){ var t=0; while(elem){t+=elem.offsetTop; elem=elem.offsetParent};return t }
var getScrollTop = function(elem){ var t=0; while(elem){t+=elem.scrollTop; elem=elem.offsetParent};return t }
setScrollTop = function(elem){
var elemT=getOffsetTop(elem), divFCT=getOffsetTop(スクロールエリア.firstChild), elemOH=elem.offsetHeight, divCH=スクロールエリア.clientHeight
var elemST=getScrollTop(elem)
if((elemT-elemST)<divFCT){
// 表示領域より↑にいる場合
スクロールエリア.scrollTop = elemT-divFCT
}
if((divFCT+divCH)<(elemT-elemST+elemOH)){
// 表示領域より↓にいる場合
スクロールエリア.scrollTop = elemT-divFCT-divCH+elemOH
}
}
})();
</script>
</html>
view raw tags.hta hosted with ❤ by GitHub


今回、dirコマンドやwhereコマンドによる検索はナシにしたので、高速にはなりました。
例えば外付けHDDに入れているファイルにタグ付けした場合、他の外付けドライブを接続して再起動した時にドライブレターが変わるとファイルPathが丸ごと無効になってしまうのですが、その場合は「D:\abc」→「E:\abc」に置換してから開く、というような「開き方」にするか、置換してから「開き方」にPathを渡す機能を実装すれば良いかと思っています。

また、暫く使ってみて何かあったら修正します。

0 件のコメント:

コメントを投稿