1 (изменено: Serge Yolkin, 2012-03-24 22:51:06)

Тема: 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='&#9658;Поиск   «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, как извлечь писалось здесь.

2

Re: HTA/JScript: Логер буфера обмена

Serge Yolkin пишет:

поиск по списку  - Ctrl+S

Почему не привычная Ctrl+F?

Serge Yolkin пишет:

Заставить окно потерять фокус в рамках JScript

window.blur();

или

document.body.blur();

Но не уверен в том, что это решит задачу потери фокуса и минимизации окна.

( 2 * b ) || ! ( 2 * b )

3

Re: HTA/JScript: Логер буфера обмена

Rumata пишет:

Почему не привычная Ctrl+F?

26-я строка снизу - поменять

if(event.keyCode==83)tt.focus();        // Ctrl+S — поиск

на

if(event.keyCode==70)tt.focus();        // Ctrl+F — поиск

Привычное у всех разное...
По поводу blur() - окно не теряет фокус, поскольку скрипт выполняется в контексте окна. М.б. и теряет, но тогда получает его обратно.

4

Re: HTA/JScript: Логер буфера обмена

2Serge Yolkin
Прикольно получилось
Правда (поскольку у меня тулбар - справа) сразу заметил мелкую ошибочку: в строках 269 и 308 надо заменить screen.width на screen.availWidth.
Ну и пара маленьких ИМХО: 1)шрифт просто огромный 2)Про Ctrl+F Rumata верно заметил. 3)Очень рекомендую googlecode для размещения своих скриптов. Супер удобно обновлять и отслеживать все изменения в коде. И самому и людям.

5 (изменено: Serge Yolkin, 2012-03-18 20:28:38)

Re: HTA/JScript: Логер буфера обмена

2mozers
Спасибо на добром слове!
За screen.width - спасибо. Для начального позиционирования сам догадался (скрипт в head), а вот при скрытии/восстановлении тупо забыл.
1), 3) бОльшая часть этого (и не только этого) творения написана на нотнике (маленькая диагональ, высокое разрешение - пиксель очень мелкий) в дороге (в основном, в машине) т.е. в офлайне. Остальная часть - на нескольких разных компах с разными настройками (на работе, например, аплоуд зарезан насмерть, правда, это настройки сети, а не компа)
2) у меня Ctrl+S настроен для быстрого фильтра в TC и быстрого поиска в AkelPad'е (QSearch), в котором всё и пишется, поэтому, для себя-любимого так привычнее. А по Ctrl+F обычно вызывается диалог поиска...
Поскольку для посетителей именно этого форума скриптинг - не китайская грамота, каждый может подправить сценарий под себя, а я готов помочь в меру своего скиллевела.

6 (изменено: Serge Yolkin, 2012-03-24 22:55:05)

Re: HTA/JScript: Логер буфера обмена

Поправил код в первом посте: для использования Ctrl+F см. строки 183 и 240 (комментить, соответственно, 182 и 239).
Заменил screen.width на screen.availWidth. Поигрался с цветами выделения и строки поиска.

24.03.12  Buffer доведён до релиза, код в первом посте обновлён. Теперь Ctrl+F и Ctrl+S работают одновременно - кому как удобнее. Подсказка по горячим кнопам - F1. Изменено отображение перевода. Все слегка причесано.