アカベコマイリ

HEAR NOTHING SEE NOTHING SAY NOTHING

Redmine テーマ minimalflat2 v1.0.0 リリース

September 06, 2015開発IcoMoon, Redmine, Stylus, テーマ

以前 minimalflat という Redmine テーマを作成したのだが、このときは標準テーマの application.css@import で取り込み必要なものだけ上書きしていた。

しかし変更する項目が多いなら上書きするよりフルスクラッチしたほうが効率的だし CSS の定義も Stylus へ移行したい。そのためコードベースを改めた新プロジェクトとして minimalflat2 を起ち上げることにした。

一ヶ月ほどかけて少しずつ開発。ひととおり実装を終えたので v1.0.0 をリリースしてみた。

更新履歴

  • 2015/9/24 v1.0.x 系リリースが更に増えたのでリンクを Releases に変更。
  • 2015/9/8 リポジトリ画面でファイルやディレクトリ部分が中央寄せになる問題や、タブのデザイン変更などをおこなった v1.0.1 をリリースした。この記事を書いてから日が浅いので、ここに公開する。

以下にテーマの特徴や開発時の雑感などをまとめる。

特徴

  1. 簡素でフラットな外観
  2. モダンな配色、今回も Flat UI の色を採用
  3. 画像のアイコン フォント化、今回は IcoMoon の IcoMoon - Free と Entypo+ を採用
  4. 折りたたみ可能なプロジェクト一覧画面
  5. favicon 対応

特徴 1 〜 3 までは minimalflat 路線を継承している。

4 は Redmine のプロジェクト数と階層が多くなっても閲覧性を損ねないための機能。標準テーマだとプロジェクトの階層は左側の線とインデントで明示される。しかし階層を持つもの同士が隣接しているとかなり見辛い。

ちょうど職場の Redmine がこの状態に陥っており対策としてツリービューっぽい折りたたみ可能な UI にする Greasemonkey スクリプトを作って配布している。しかしインストール手順書を用意しても導入を難しく感じるスタッフがけっこういた。

ならばテーマ側で同等の機能を提供してみよう。というわけで theme.js に実装してみた。Greasemonkey だと CSS をスクリプト内に定義していたけど theme.js ならテーマに同梱されることが前提となるため CSS を分離して処理がかなりスッキリした。

プロジェクト一覧の外観はこんな感じ。

プロジェクト一覧

階層を持つプロジェクトはタイトル左へ +/- アイコンを表示。タイトルの行をクリックすると展開状態がトグル式に切り替わる。

favicon は Redmine 2.5 以降の新機能。Patch #15689: Make favicon themeable で対応された。テーマの favicon ディレクトリ内に favicon.ico を配置するとそれを設定してくれる。今回は IcoMoon の Entypo+ に含まれるロケットの SVG を元に作成。

その他、特徴というほどでもないのだがコードのシンタックス ハイライト配色を mbadolato/iTerm2-Color-Schemes の Espresso 風にしてみた。

構文強調

職場の Redmine ではチケットや Wiki にサンプル コードを記載することが多い。そのため配色を愛用している iTerm か Sublime Text と統一したかった。iTerm は配色パターンが少なく取り入れやすそうだったので、こちらを採用。

開発環境

Redmine テーマは CSS と JavaScript が主なため Web フロントエンド開発でおなじみの Node と gulp で環境構築。とりあえず gulp で、という感じで採用したのだがファイル監視と自動コンパイルの対象は Stylus のみだから package.json で十分だったかも。

テーマの動作確認はダミー HTML と VM 上の Redmine で実施。前にテーマを作成したときにも遭遇したのだが、テーマの構成ファイルが更新された後にブラウザでスーパー リロードしたり Redmine を再起動 (REDMINEDIR/tmp/restart.txt を作成したあとにブラウザから再アクセス) しても変更が反映されないことがある。

そのため Redmine の各画面を保存したダミー HTML を作成。基本的な確認はこちらでおこなっている。とはいえダミー HTML であらゆるパターンを網羅するのは難しいため、きりのよいところで VM 上の Redmine に移行して動作確認するようにした。

VM は Vagrant で管理している。イメージは以下を採用。

CentOS 上の /var/lib/redmine に Redmine がインストールされている。私が自前で構築する場合と一緒なので馴染みやすかった。開発ディレクトリと VM 上のテーマ ディレクトリは Vagrantfile にて以下のように設定。

  # Share an additional folder to the guest VM. The first argument is
  # the path on the host to the actual folder. The second argument is
  # the path on the guest to mount the folder. And the optional third
  # argument is a set of non-required options.
  config.vm.synced_folder "./src", "/var/lib/redmine/public/themes/minimalflat2"

別のマシンでも開発できるように Vagrantfile もリポジトリに含めている。今回は Redmine 3.0 だけを対象とした。config.vm.define を利用すると単一 Vagrantfile で複数の VM を管理可能。なので 2.x と 3.x の最新をチェックしたほうがよかったかも。

画像のアイコン フォント化

Redmine で表示される様々なアイコンは PNG ファイルとなっている。これをアイコン フォントへ置き換える方法をまとめる。

まずは標準テーマで使用される画像に対応するアイコン フォントを用意。画像は Redmine 本体をダウンロードして /public/images から確認可能。ここにあるもので IcoMoon により置き換えられそうな図案をひとつずつ選択。代替がなければ意味の近そうなものを選ぶか色などでバリエーションを付けることで妥協する。

IcoMoon で Generate Font すればアイコンとなるフォントと style.css を入手できる。フォントはテーマの fonts ディレクトリに置き style.css は Stylus 化しておく。style.css の初期状態は以下のような感じ。

@font-face {
  /* フォントの設定 */
}

[class^="icon-"], [class*=" icon-"] {
  /* アイコン共通設定 */
}

.icon-app:before {
  content: "\e600";
}

フォント設定はそのまま流用。アイコン共通設定は Stylus の Mix-In へ書き換える。content にアイコンのユニコード、color で色を指定。

icon( content, color ) {
  font-family "icon"
  speak none
  font-style normal
  font-weight normal
  font-variant normal
  text-transform none
  line-height 1

  // Better Font Rendering
  -webkit-font-smoothing antialiased
  -moz-osx-font-smoothing grayscale

  content content
  color color
  margin-right .2em
}

次にアイコンの指定を Stylus の変数にする。これはテキスト エディターで一括置換すると楽でよい。

icon-app = "\e600"

こうすることでアイコン指定を以下のように定義できる。これで backgroudbackground-image により画像設定している箇所を代替してゆく。

.icon-projects:before {
  icon( icon_app, color_blue )
}

アイコンは before だけでなく after 表示したくなることもある。そのため Mix-In にしておくことで柔軟性を高めた。アイコンを示すユニコードは変数化されているため Mix-In せず自前で content を指定してもよいだろう。

img タグのアイコン フォント置換

前のテーマと同様に <img> タグで直に画像配置しているものをアイコン フォントに置き換える。theme.js に置換用の関数を定義して、

function buttonIcon( target, iconClass ) {
  $( target ).each( function() {
    $( '<i>' ).addClass( iconClass ).prependTo( $( this ) );
  } );
}

function buttonText( target, text ) {
  $( target ).each( function() {
    $( '<strong>' ).text( text ).prependTo( $( this ) );
  } );
}

function imageToIcon( target, icon ) {
  $( target ).each( function() {
    var title = $( this ).attr( 'alt' );
    $( this ).replaceWith( $( '<i>' ).addClass( icon ).attr( 'title', title ) );
  } );
}

以下のように実行。

// Editor toolbar
$( '.jstElements' ).removeClass( 'jstElements' ).addClass( 'js-replace-jstElements' );
buttonIcon( '.jstb_strong', 'js-replace-icon-strong' );
buttonText( '.jstb_code', 'C' );

// img
imageToIcon( 'img[src*="toggle_check.png"]', 'js-replace-icon-check' );
imageToIcon( 'img[src*="true.png"]', 'js-replace-icon-check' );

Editor toolbar とある部分はチケット編集などのエディタで使用されている JSToolbar を置換している。アイコン フォントで代替できないもの (H1 など) はテキストをそのまま指定。

<img> タグは src の内容を見て <i> タグに置換。年月日を入力するためのカレンダー用アイコンは対象外とした。詳細は calendar.png をアイコン フォントで置き換える を参照のこと。いずれ自前でカレンダー処理することで置き換えを再検討するかもしれない。

<img> タグは Redmine 本体や jQuery UI などにより埋め込まれるため網羅するのが難しい。そのためかなり苦労した。

Redline も WordPress のようにページ構造自体をテンプレート化してテーマ側から構築できるとこの種の問題を解決できるのだが。しかしそうすると過去テーマの互換性がなくなるから厳しいかな。しばらくは現状の設計で我慢するしかなさそうだ。