Hatena::Groupkeysnail

きすねた(ん)

2012-02-12

imenu 的な何か

| 18:56 | imenu 的な何か - きすねた(ん) を含むブックマーク はてなブックマーク - imenu 的な何か - きすねた(ん)

f:id:mooz:20120212185507p:image

ヘッダ (h1 ~ h4) を一覧表示し,その位置までスクロールできるように.Emacs でいうところの imenu のようなもの.

ext.add("imenu-headers", function () {
  let anchorSelector = [
    "h1",
    "h2",
    "h3",
    "h4"
  ].join(",");

  let elements = Array.slice(content.document.querySelectorAll(anchorSelector));

  function elementToString(element) {
    let headerString = "",
        matched = null;
    if ((matched = element.localName.match(/h([0-9])/))) {
      let headerCount = parseInt(matched[1], 10);
      headerString = (new Array(headerCount)).join("  ");

      let headerMarks = {
        1: '',            /* none */
        2: "\u2023",      /* right arrow */
        3: "\u2022",      /* bullet */
        4: "\u25E6"       /* white bullet */
      };

      if (headerMarks[headerCount])
        headerString = headerString + headerMarks[headerCount] + " ";
    }

    return headerString + element.textContent;
  }

  function scrollToElement(element) {
    let anchor = element.getAttribute("id") || element.getAttribute("name");
    if (anchor)
      content.location.hash = anchor;
    else
      element.scrollIntoView();
  }

  prompt.selector({
    message: "jump to: ",
    collection: elements.map(function (element) elementToString(element)),
    callback: function (selectedIndex) {
      if (selectedIndex < 0)
        return;
      scrollToElement(elements[selectedIndex]);
    }
  });
}, "imenu-headers", true);

私は Emacs で imenu を M-i に割り当てているので,同じキーへ割り当ててみた.なかなか便利.

key.setGlobalKey("M-i", function (ev) {
  ext.exec("imenu-headers");
}, 'jump to headers');

2011-11-29

プロンプト内でスペルチェック

| 12:30 | プロンプト内でスペルチェック - きすねた(ん) を含むブックマーク はてなブックマーク - プロンプト内でスペルチェック - きすねた(ん)

.keysnail.js に以下を.

let (p = document.querySelector("#keysnail-prompt-textbox")) {
  p && p.setAttribute("spellcheck", "true");
};

2011-04-23

プロンプトへフォーカス / コンテンツへフォーカスを一つのキーバインドで

| 18:22 | プロンプトへフォーカス / コンテンツへフォーカスを一つのキーバインドで - きすねた(ん) を含むブックマーク はてなブックマーク - プロンプトへフォーカス / コンテンツへフォーカスを一つのキーバインドで - きすねた(ん)

key.setGlobalKey(['C-c', 'p'], function (ev, arg) {
    var kpt = document.getElementById("keysnail-prompt-textbox");

    if (ev.target === kpt) {
        kpt.blur();
        gBrowser.focus();
        content.focus();
    } else {
        !document.getElementById("keysnail-prompt").hidden && kpt.focus();
    }
}, 'Toggle focus prompt');

プロンプトへフォーカスしている時に document.commandDispatcher.focusedElementev.originalTarget で得られるノードXULtextbox#keysnail-prompt-textbox でなく,その実装に用いられている HTMLinput 要素になってしまう.そのため,わざわざ ev.target としている.

Node.compareDocumentPositionが素晴らしい - hogehoge @teramako を使って inputtextbox に含まれているかを判断しても良かったのだが,面倒であるし,匿名要素に対してもうまく動くのか不明だったため止めた.

2011-04-03

ポップアップにおける画像の大きさを固定,および nsIAlertsService の問題に関して

| 12:29 | ポップアップにおける画像の大きさを固定,および nsIAlertsService の問題に関して - きすねた(ん) を含むブックマーク はてなブックマーク - ポップアップにおける画像の大きさを固定,および nsIAlertsService の問題に関して - きすねた(ん)

大きさ固定

Twitter クライアントプラグインポップアップにおけるアイコンの大きさを固定する方法が FAQ なので.改めて取り上げる.

PRESERVE エリアへ以下の記述を.

style.register(<><![CDATA[
    @-moz-document url("chrome://global/content/alerts/alert.xul") {
        image#alertImage {
            max-width  : 48px !important;
            max-height : 48px !important;
        }
    }
]]></>);

nsIAlertsService

Twitter クライアントプラグインポップアップ表示には nsIAlertsService を使っている.

この nsIAlertsService だが,バックエンドとして Mac OS X では Growl を使い(インストールされていれば),Linux などにおける X 環境では notify-osd や notification-daemon を使う.こうしたバックエンドはきっちりしているものなので,問題はあまりない.(notify-osd の悲惨さについてはまたの機会に語るとしよう)

一方 Windows では(何も入れていなければ) XUL による Firefox 独自のポップアップが使われるのだが,この XUL によるポップアップがクセモノで,確認している限りでも次のような欠点が存在する.

  • 文字の折り返しが効かない
  • 画像の大きさが制限されていない
    • 1024x1024 px など巨大な画像が指定されていた日には大変悲惨なこととなる

折り返しが効かない

一つめの折り返しが効かない,という問題に対処することはなかなか難しい.説明のため,まずポップアップに使われている XUL ファイルの内容を抜粋して以下に示す.この内容は Firefoxview-source:chrome://global/content/alerts/alert.xul という URI を指定すれば閲覧することができる.

<window id="alertNotification"
        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
        windowtype="alert:alert"
        xmlns:xhtml="http://www.w3.org/1999/xhtml"
        xhtml:role="alert"
        pack="start"
        onload="onAlertLoad()"
        onclick="onAlertClick();">
  
  <script type="application/javascript" src="chrome://global/content/alerts/alert.js"/>

  <box id="alertBox" class="alertBox">
    
    <hbox class="alertImageBox" align="center" pack="center">
      <image id="alertImage"/>
    </hbox>

    <vbox id="alertTextBox" class="alertTextBox">
      <label id="alertTitleLabel" class="alertTitle plain"/>
      <label id="alertTextLabel" class="alertText plain"/>
    </vbox>

  </box>

  <!-- This method is called inline because we want to make sure we establish the width
       and height of the alert before we fire the onload handler. -->
  <script type="application/javascript">prefillAlertInfo();</script>

</window>

ここで注目すべきは次の部分だ.

  <box id="alertBox" class="alertBox">
    
    <hbox class="alertImageBox" align="center" pack="center">
      <image id="alertImage"/>
    </hbox>

    <vbox id="alertTextBox" class="alertTextBox">
      <label id="alertTitleLabel" class="alertTitle plain"/>
      <label id="alertTextLabel" class="alertText plain"/>
    </vbox>

  </box>

これを見ると,ポップアップのタイトルとメッセージには label 要素が使われている,ということが分かる.

この label 要素がクセモノで,value 属性にテキストが指定された場合は折り返しが一切なされず,内部テキストノードへテキストを与えた場合には折り返しがなされる,という性質を持つ.実例で示すと,以下のようになる.(参考:Piro|4/9技術書典2き06さんのツイート: "@stillpedant labelとdescriptionは、value属性で文字列を指定した場合は「1行で表示・crop属性に従って末尾を省略」、flex="1"で子要素にテキストノードを置いた場合は「指定幅で折り返して複数行表示」ですよ。と、

<label value="折り返しがなされない" />

<label>折り返しがなされる</label>

さて,先程の XUL における nsIAlertsService が何をやっているかというと,これは view-source:chrome://global/content/alerts/alert.js を見ると分かるが,次のようにして label の value 属性に値を設定している.つまり,これではどう頑張っても折り返しは出来ない.

document.getElementById('alertTextLabel').setAttribute('value', window.arguments[2]);
// 中略
document.getElementById('alertTitleLabel').setAttribute('value', window.arguments[1]);

この問題に無理やり対処する方法としては,ポップアップが表示される瞬間に上記要素の value 属性に設定されたテキストを取得し,その value 属性を削除した後に label の子テキストノードとしてそのテキストを append する,というものが考えられる.実現には userChrome.js のような仕組みを用いる必要があり,とても面倒だ.

画像の大きさが制限されていない

画像の大きさが制限されていないという問題に対しては,幸いながら比較的簡単な対処法が存在する.nsIStyleSheetService の仕組みを使い,CSS で強制的に画像のサイズを制限してしまえば良い.KeySnail の style モジュールを使う方法に関しては,冒頭に示したとおりだ.

まとめ

nsIAlertsService の XUL を用いたポップアップは非常に出来が悪い.これを改善する方法が,これまでにもいくつか提案されているようだ.

Windows でも Growl for Windows のようなソフトウェアを導入した方が幸せになれるのではないかと,個人的には感じる.

2011-04-01

Tanything など prompt.selector() をあらかじめ編集モード ON (キーマップ無効) で起動

| 18:54 | Tanything など prompt.selector() をあらかじめ編集モード ON (キーマップ無効) で起動 - きすねた(ん) を含むブックマーク はてなブックマーク - Tanything など prompt.selector() をあらかじめ編集モード ON (キーマップ無効) で起動 - きすねた(ん)

Tanything を普段は C-a に割り当て j, k, g, G といった less ライクなキーバインドで利用している.

これを単純な switch-to-buffer として使おうと考えた場合,上記の less ライクなキーバインドを無効にして編集モードへ移行するために C-z を入力する必要があり,これが非常な手間となっていた.

何とかならないか,と考えていたところ prompt.selector の編集モードは,外部から prompt.editModeEnabled として触ることができることを思い出した.すっかり忘れていた.

そこで,以下のようにしてみると,うまく Tanything が編集モードで起動してくれた.

ext.exec("tanything");
prompt.editModeEnabled = true;

個人的な設定を以下に示す.

C-x b文字列絞り込みによるタブ移動のために,C-a はタブ一覧を眺めて削除やピン留め*1などの操作を行なうために,それぞれ利用する.

key.defineKey([key.modes.VIEW, key.modes.CARET], 'C-a', function (ev, arg) {
    ext.exec("tanything", arg);
}, 'タブを一覧表示', true);

key.setGlobalKey(['C-x', 'b'], function (ev, arg) {
    ext.exec("tanything", arg);
    prompt.editModeEnabled = true;
}, 'タブを一覧表示 (編集モード)', true);

*1:Tanything 0.1.4 以上では Firefox 4 で導入されたタブのピン留め機能を操作することが可能になっている

IrelandIreland2011/10/06 14:45Great aitrcle, thank you again for writing.