Тема: HTA/JScript: Логер буфера обмена
Простенький сценарий, являющийся чем-то вроде менеджера буфера обмена. Мониторит буфер и добавляет в список (лог) его новое содержимое (только текст). Список редактируемый - можно править перед дальнейшим использованием.
Работает поиск по списку (спасибо уважаемому mozers™) - Ctrl+S или клик на строке поиска в верхней части окна. Найденное подсвечивается сразу, для перехода к следующему вхожению - Enter. Соответственно, Enter для редактирования (новый абзац), работает только при пустой (серое приглашение) строке поиска.
Дополнительная фича - онлайн перевод (взят целиком из другого моего сценария) - Ctrl+T или клик на кнопке с иконкой Bing (если иконка на кнопке не отображается - сервис недоступен). Логика: если есть выделение в списке - будем переводить выделение, если выделения нет - проверяем, нет ли чего в самом первом абзаце (обычно он пустой) и, если есть, переводим - это для перевода "с руки", без копирования в буфер. Если и там - ничего, ничего и не переводим.
Esc при активном окне сценария или клик на кнопке [_] прячет окно (на самом деле просто двигает его за пределы экрана). Чтобы снова отобразить надо кликнуть на кнопке скрипта в панели задач, или дойти до него по Alt+T. ! Под Win7 перед тем, как вызывать скрытое окно сценария, надо где-нибудь кликнуть - на десктопе, окне или кнопке любого другого приложения, свободном месте панели задач! Дело в том, что отображение скрытого окна привязано к событию onfocus, а без переключения на другое окно сценарий фокус не теряет, соответственно, и не получает. Заставить окно потерять фокус в рамках JScript мне не удалось.
В данной версии не сохраняет сессию - надо повозиться с ограничением размера файла при сохранении, а то у тех, кого ломает порядок наводить, файл с сохраненным списком очень скоро может стать безразмерным. - относилось к предыдущей версии. Теперь история сохраняется, но не ограничивается. Чистить лог прийдется самостоятельно, или по окончанию очередного проекта снесите файл Buffer.log
Buffer.hta
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<!-- Простенький менеджер буфера обмена с онлайн переводчиком -->
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta http-equiv="MSThemeCompatible" content="yes" />
<meta http-equiv="X-UA-Compatible" content="IE=8" />
<hta:application
applicationName="BLingvist-XVIc"
border="dialog"
borderStyle="complex"
caption="no"
icon="%SystemRoot%/system32/ctfmon.exe"
innerBorder="no"
id="HTApp"
navigable="no"
singleInstance="yes"
version="1.21"
/>
<script type="text/jscript" language="JScript">
self.resizeTo(400,screen.availHeight);
self.moveTo(screen.availWidth-400,0);
</script>
<style type="text/css">
*{margin:0;padding:0;}
body{background-color:infobackground;}
button{color:blue;font-family:Webdings;width:40px;}
button,div{position:absolute;}
button{height:25px;top:0;}
div,#bt{left:0;}
div,#be{right:0;}
p{border-bottom:solid 1px silver;padding-bottom:8px;word-wrap:break-word;}
p,#tt{font-family:Arial;font-size:16px;}
span{font-weight:bold;}
#bc{right:39px;}
#be{color:crimson;}
#tt{border:none;height:19px;margin:3px 84px 0 44px;overflow:hidden;padding-left:4px;top:0;width:265px;white-space:nowrap;}
#ws{border-top:solid 1px silver;bottom:0;color:navy;overflow-y:scroll;padding:8px;top:25px;}
</style>
</head>
<body>
<div id="tt" contenteditable="true"></div>
<button hidefocus="true" id="bt" onmousedown="fg();" title="Перевод «Ctrl+T»" type="button">
<img alt="M$" src="http://www.microsofttranslator.com/favicon.ico" style="margin-top:1px;" />
</button>
<button hidefocus="true" id="bc" onclick="self.moveTo(screen.width,0);" title="Скрыть «Esc»" type="button">0</button>
<button hidefocus="true" id="be" onclick="window.close();" title="Выход «Alt+F4»" type="button">r</button>
<div id="ws" contenteditable="false"></div>
</body>
<script type="text/jscript" language="JScript">
var
Arr=new Array(), // массив фрагментов для перевода
arq, // осталось перевести
cd=window.clipboardData, // clipboardData
co='', // старое значение буфера обмена
cn='', // новое значение буфера обмена
fng=0, // поиск — текущее вхождение
fnq, // поиск — найдено вхождений
ld=document.createElement('script'), // сервис MicrosoftTranslator
le=new ActiveXObject('WScript.Shell'),
lf=new ActiveXObject('Scripting.FileSystemObject'),
lfn=HTApp.commandLine.replace(/^"([^"]+)\.hta"\s*.*$/,'$1')+'.log',
li, // язык на входе
lo, // язык на выходе
tit=document.title='«Буфер '+HTApp.version+'»',
tr, // TextRange
ts, // HighLight Range (перевод)
mid='B5C728A51E78F96A9277D318720A4BD9FC0F3EB3';
// при написании собственных скриптов получите пожалуйста
// собственный Bing id (это бесплатно):
if(lf.fileExists(lfn)){ // если сохранен предыдущий сеанс
with(lf.OpenTextFile(lfn)){ // загружаем сохраненное
if(!AtEndOfStream)ws.innerHTML=ReadAll();
Close();
}
// проверяем, не сохранено ли текущее значение буфера
try{ // на случай, если буфер пуст
if(cd.getData('text').replace(/\s/g,'')==ws.all.tags('p')[1].innerText.replace(/\s/g,'')){
co=cd.getData('text');
}
}catch(e){}
}
fz();
// функции:
function fd(){ // выделить вхождение
tr.findText(tt.innerText);
tr.select();
tr.collapse(false);
}
function ff(){ // подсветить все
tr=null;
ws.innerHTML=fs();
tt.innerHTML=tt.innerText;
tt.style.backgroundColor='';
if(tt.innerText.replace(/\s/g,'')=='')return;
tr=document.body.createTextRange();
for(var i=0;tr.findText(tt.innerText);i++){
if(tr.parentElement().tagName=='P')tr.execCommand('backColor','','#bf7');
tr.collapse(false);
}
fnq=i
if(fnq==1&&tt.innerText)tt.style.backgroundColor='#fbd';
if(fnq==2&&tt.innerText)tt.style.backgroundColor='#bdf';
}
function fg(){ // определяем условия перевода
var sel;
ts=document.selection.createRange()
if(ts.text.replace(/\s/g,'')){ // если есть выделение — будем переводить его
sel=ts.text;
}else{ // иначе — содержимое первого параграфа
ts=null;
sel=ws.firstChild.innerText;
}
if(sel.replace(/\s/g,'')){
sel=sel.replace(/^\s*|\s*$|[\f\r\v\0]/g,'').replace(/\s*\n\s*/g,'\n');
ld.src='http://api.microsofttranslator.com/V2/Ajax.svc/Detect?oncomplete=cb&appId='+mid+'&text='+sel.substring(0,100);
window.cb=function(e){
li=e;
lo=(e=='ru')?'en':'ru';
fj(sel);
};
document.all.tags('html')[0].appendChild(ld);
}else{ // если нечего переводить — курсор на первый параграф
ws.firstChild.focus();
return;
}
tt.style.backgroundColor='#fa5';
}
function fj(e){ // предобработка и перевод
var
rx=new RegExp('.+','g'),
res,
rsl,
pp;
while((rsl=rx.exec(e))!=null){ // разбиваем на абзацы
var
rcr=true,
arg=rsl[0].replace(/\s+/g,' ');
Arr.length?pp=true:pp=false;
while(rcr){
if(arg.length>650){ // бьём слишком длинные абзацы
var pt=0;
rx.compile(/(\.|\!|\?)\s/g);
while((res=rx.exec(arg.substring(0,650)))!=null)pt=rx.lastIndex;
if(pt<300){
rx.compile(/\s/g);
while((res=rx.exec(arg.substring(0,650)))!=null)pt=rx.lastIndex;
}
Arr[Arr.length]={t:arg.substring(0,pt),r:'',i:false};
arg=arg.substring(pt,arg.length);
}else{
Arr[Arr.length]={t:arg,r:'',i:pp};
rcr=false;
}
}
}
arq=Arr.length;
for(var i=0;i<arq;i++){ // отправляем на перевод
var s=document.createElement('script');
s.id='cs'+i;
s.src='http://api.microsofttranslator.com/V2/Ajax.svc/Translate?oncomplete=cb'+i+'&appId='+mid+'&from='+li+'&to='+lo+'&text='+Arr[i].t;
document.all.tags('html')[0].appendChild(s);
window.execScript('window.cb'+i+'=new Function("e","Arr['+i+'].r=e;if(--arq==0)fr();");');
}
}
function fp(){ // создать параграф
var t=document.createElement('p');
t.contentEditable=true;
return(t);
}
function fr(){ // формируем оригинал и перевод
var t='',u='',v=' ',w;
for(var i=0;i<Arr.length;i++){
if(Arr[i].i){
t+='<br />'+Arr[i].r;
u+='<br />'+Arr[i].t;
v='<br />';
}else{
t+=Arr[i].r;
u+=Arr[i].t;
}
}
w='<span style="color:blue;font-weight:bold;">'+u+
'</span>'+v+'<span style="color:crimson;">[ '+
li+'>'+lo+v+t+' ]</span>';
if(ts){ // если перевели выделение
ts.pasteHTML(w+v);
}else{ // если перевели первый параграф
with(ws){
firstChild.innerHTML=w;
insertBefore(fp(),firstChild);
}
}
Arr.length=0;
ts=null;
tt.style.backgroundColor='';
}
function fs(){ // удалить подсветку
return(ws.innerHTML.toString().replace(/<\/?font[^>]*>/gi,''));
}
function fu(){ // стоп вниз по Tab
if(document.activeElement.nodeName!='P')ws.childNodes[ws.childNodes.length-1].focus();
}
function fv(){ // стоп вверх по Tab
if(document.activeElement.nodeName!='P')ws.firstChild.focus();
}
function fz(){ // подсказка в строке поиска
if(tt.innerText==''){
tt.style.color='silver';
tt.innerText='►Поиск «Ctrl+S, Ctrl+F»';
}
}
tt.onblur=function(){
fz();
}
tt.onfocus=function(){
if(tt.style.color=='silver'){
tt.style.color='navy';
tt.innerText='';
}
}
tt.onkeypress=function(){
if(event.keyCode){
fng=0;
window.setTimeout('ff()',50);
}
}
tt.onkeyup=function(){
if(event.altKey||event.ctrlKey||event.shiftKey)return;
if(event.keyCode==8||event.keyCode==46){ // BackSpace или Delete
window.setTimeout('ff()',50);
}
}
tt.onpaste=function(){
event.cancelBubble=true;
// при вставке в строку поиска из буфера вставляется только первая строка:
tt.innerText=cd.getData('text').replace(/^\s*([^\s][^\n]*)(\s*.*)*$/,'$1');
fng=0;
window.setTimeout('ff()',50);
return(false);
}
ws.oncopy=function(){
cd.setData('text',co=document.selection.createRange().text.replace(/\s*$/,''));
return(false);
}
window.onbeforeunload=function(){ // сохранить историю
if(ws.innerText&&le.popUp('Сохранить историю?',0,tit,4148)==6){
var t=lf.OpenTextFile(lfn,2,true);
t.Write(fs()); // ограничить длину сохраняемого ??? fs().length
t.Close(); // или создавать файлы-архивы ??? ~ 40 000 bytes
}
}
window.onfocus=function(){ // показать после скрытия
self.moveTo(screen.availWidth-400,0);
}
window.onload=function(){ // захват буфера обмена
if(!ws.childNodes.length){
ws.appendChild(fp());
ws.firstChild.focus();
}
window.setTimeout(arguments.callee,200); // самовызов
cn=cd.getData('text');
cn=cn?cn.replace(/-{4,}|={4,}|\*{4,}/g,''):'';
// если буфер не пуст и изменился:
if(cn.replace(/\s/g,'')&&cn.replace(/\s/g,'')!=co.replace(/\s/g,'')){
co=cn;
with(ws){
if(firstChild.innerText){
firstChild.innerText=firstChild.innerText.replace(/\s*$/,'');
insertBefore(fp(),firstChild);
}
firstChild.innerText=co.replace(/[\n\r]+/g,'\n').replace(/\s*$/,'');
insertBefore(fp(),firstChild);
blur();
fz();
firstChild.focus();
}
}
}
document.onkeydown=function(){ // горячие клавиши
var acs=100*event.altKey+10*event.ctrlKey+1*event.shiftKey;
switch(event.keyCode){
case 27: // Esc скрыть
self.moveTo(screen.availWidth,0);
return;
case 112: // F1 справка
le.popUp(hl.innerHTML,0,tit,32);
return;
case 84: // Ctrl+T перевод
if(acs!=10)return;
case 113: // F2, Ctrl+T перевод
fg();
return;
case 13: // Enter \n вместо <p>
if(acs==1||tt.style.color=='silver'){
// если не в поиске, по Shift+Enter — даже если в поиске
document.selection.createRange().text='\n';
return(false);
}
case 114: // F3, Enter искать далее
if(!acs&&tt.innerText&&tt.style.color!='silver'){
if(fng==0){
tr=null;
tr=document.body.createTextRange();
fd();
// исключаем символы на кнопках
if(tt.innerText=='0'||tt.innerText=='r'){
fd();
++fng;
}
}
fd();
if(++fng==fnq-1)fng=0;
}
return(false);
case 69: // Ctrl+E объединить текущий со следующим
if(acs!=10)return;
case 116: // F5, Ctrl+E объединить текущий со следующим
event.returnValue=false;
var t=document.activeElement;
if(t.tagName!='P'){
if(t.parentElement=='P')t=t.parentElement;
else return(false);
}
if(t.nextSibling){
t.innerText+='\n'+t.nextSibling.innerText+'\n';
ws.removeChild(t.nextSibling);
}
return(false);
case 46: // Ctrl+Shift+Del очистить историю
if(acs==11){
if(le.popUp('Очистить историю?\nВсе данные будут потеряны!',3,tit,4116)!=6)return;
if(lf.fileExists(lfn))lf.deleteFile(lfn);
ws.innerHTML='<p contenteditable> </p>';
ws.firstChild.focus();
ws.firstChild.innerText=tt.innerHTML='';
tt.style.backgroundColor='';
return;
}else if(acs!=10)return;
case 119: // F8, Ctrl+Del удалить параграф
var t=document.activeElement;
if(t.tagName!='P'){
if(t.parentElement=='P')t=t.parentElement;
else return(false);
}
if(t.nextSibling)t.nextSibling.focus();
else if(t.previousSibling)t.previousSibling.focus();
else return(false);
ws.removeChild(t);
return(false);
case 70: // Ctrl+F поиск
case 83: // Ctrl+F, Ctrl+S поиск
if(acs!=10)return;
var sel=document.selection.createRange().text;
tt.focus();
if(sel){ // если есть выделение — поиск по выделению
tt.style.color='navy';
tt.innerText=sel.replace(/^\s*([^\s][^\n]*)(\s*.*)*$/,'$1');
fng=0;
window.setTimeout('ff()',50);
}else{ // если нет — выделить строку поиска
document.execCommand('selectAll');
}
sel=null;
return(false);
case 67: // Ctrl+Shift+C копировать всю историю (только текст)
if(acs==11){
var t='';
for(var i=0;i<ws.childNodes.length;i++){
if(ws.childNodes[i].innerText)t+=ws.childNodes[i].innerText+'\n';
}
t+='\n';
cd.setData('text',co=t);
return(false);
}else return;
case 9: // Tab, Shift+Tab не выпускать фокус из ws
if(acs==0)window.setTimeout('fu()',1);
else if(acs==1)window.setTimeout('fv()',1);
else return(false);
}
}
</script>
<comment id="hl">История буфера обмена
Особенности: при копировании из истории фрагмент
копируется без стилей только текст; кнопка Tab
(Shift+Tab) перемещает фокус в пределах истории,
доступ к другим элементам интерфейса — с помощью
мыши, или горячих клавишей:
Справка F1
Перевод F2, Ctrl+T*
Поиск Ctrl+F, Ctrl+S*
Искать далее F3, Enter
Абзац в режиме поиска Shift+Enter
Скрыть Esc
Копировать историю Ctrl+Shift+C
Очистить историю Ctrl+Shift+Del
Выход Alt+F4
управление параграфами:
Следующий Tab
Предыдующий Shift+Tab
Выделить Ctrl+A
Объединить следующий F5 Ctrl+E
Удалить F8, Ctrl+Del
* при наличии выделения в истории поиск/перевод
осуществляются по тексту выделения, иначе — фокус
помещается в строку поиска, или в первый параграф
соответственно.
Сергей Ёлкин (2012)
</comment>
</html>
если что-то съехало (я использую табуляции), здесь можно взять архив 7z в base64, как извлечь писалось здесь.