12
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

RedmineのView Customizeを用いたJavaScript OSSの組み込み

Last updated at Posted at 2019-05-18

目的

Github上には大量のJavaScript OSSが公開されており、それらをRedmineに組み込めれば機能を大きく拡張できるようになる。

redmine_view_customizeプラグインはv2.1.0から従来のCSS/JavaScriptに加えHTMLを埋め込めるようになった。
この機能とプロジェクトの添付ファイルを利用してRedmineのシステム管理者権限のみで(つまりサーバーへログインせずに)JavaScript OSSを組み込める。

この記事では例としてJavaScriptのOSS「BigPicture.js」を組み込み、チケットの添付ファイルの画像/音声/動画のプレビュー機能を強化する

Redmine 4.0.3とView Customize v2.1.0で確認した。

スクリプト配置用プロジェクトの作成

スクリプト配置用プロジェクトを作成する。ここでは「ViewCustomizeVault」とした。

このプロジェクトの「ファイル」メニューにJavaScriptを配置する。
BigPicture.jsでは BigPicture.min.js を添付する。

Greenshot 2019-05-17 18.36.46.png

View Customizeの設定

BigPicture.min.jsとビジネスロジックを埋め込む。

<script>タグのsrc属性には添付した BigPicture.min.js のダウンロードURLを入れる。

このコードでは画像/音声/動画を内部で判別して該当するプレビューAPIを呼び出している。
実際に表示可能かどうかはブラウザ次第になるものの、ブラウザのアドオンやプラグイン側でプレビュー機能を拡張することで追従しやすくなる。

  • Path pattern: /issues/[0-9]+
  • Type: HTML
<script src="/redmine/attachments/download/1/BigPicture.min.js"></script>
<script>
window.addEventListener('load', function() {
    const mimeTypes = {
        "apng": "image/apng",
        "bmp": "image/x-ms-bmp",
        "gif": "image/gif",
        "jpeg": "image/jpeg",
        "jpg": "image/jpeg",
        "ktx": "image/ktx",
        "pic": "image/x-pict",
        "png": "image/png",
        "psd": "image/vnd.adobe.photoshop",
        "rgb": "image/x-rgb",
        "svg": "image/svg+xml",
        "svgz": "image/svg+xml",
        "tfx": "image/tiff-fx",
        "tga": "image/x-tga",
        "tif": "image/tiff",
        "tiff": "image/tiff",

        "aac": "audio/x-aac",
        "aif": "audio/x-aiff",
        "aiff": "audio/x-aiff",
        "m3a": "audio/mpeg",
        "m4a": "audio/x-m4a",
        "mid": "audio/midi",
        "midi": "audio/midi",
        "mp3": "audio/mpeg",
        "mp4a": "audio/mp4",
        "oga": "audio/ogg",
        "ogg": "audio/ogg",
        "wav": "audio/x-wav",
        "weba": "audio/webm",
        "wma": "audio/x-ms-wma",

        "asf": "video/x-ms-asf",
        "asx": "video/x-ms-asf",
        "avi": "video/x-msvideo",
        "h264": "video/h264",
        "jpgv": "video/jpeg",
        "m2v": "video/mpeg",
        "m4v": "video/x-m4v",
        "mov": "video/quicktime",
        "mp4": "video/mp4",
        "mp4v": "video/mp4",
        "mpeg": "video/mpeg",
        "mpg": "video/mpeg",
        "mpg4": "video/mp4",
        "ogv": "video/ogg",
        "webm": "video/webm",
        "wmv": "video/x-ms-wmv",
        "wmx": "video/x-ms-wmx"
    };

    var click_OpenThumbnail = function(e){
        const ext = e.target.getAttribute('data-dp').split(".").pop();
        const mime = mimeTypes[ext];
        if(/^image\//.test(mime)){
            BigPicture({el: e.target, imgSrc:e.target.getAttribute('data-dp')});
        }
        else if(/^audio\//.test(mime)){
            BigPicture({el: e.target, audio:e.target.getAttribute('data-dp')});
        }
        else if(/^video\//.test(mime)){
            BigPicture({el: e.target, vidSrc:e.target.getAttribute('data-dp')});
        }
        else{
            BigPicture({el: e.target, iframeSrc:e.target.getAttribute('data-dp')});
        }
        e.preventDefault();
    }

    document.querySelectorAll('a:not(.icon-only)[href*="/attachments/"]').forEach(link => {
        const src = link.pathname.replace("/attachments/", "/attachments/download/") + "/" + link.text;
        const thumb = link.pathname.replace("/attachments/", "/attachments/thumbnail/")+"/200";

        link.setAttribute('data-dp', src);
        link.setAttribute('data-caption', link.text);
        link.addEventListener('click', click_OpenThumbnail);

        // create thumbnail
        const ext = src.split(".").pop();
        const mime = mimeTypes[ext];
        if(/^image\//.test(mime)){
            let img = document.createElement("img");
            img.src = thumb;
            img.alt = link.text;
            img.style = "max-width: 100px; max-height: 100px;";
            img.setAttribute('data-dp', src);
            img.setAttribute('data-caption', link.text);
            img.addEventListener('click', click_OpenThumbnail);
            link.insertBefore(img, link.firstChild);
        }
        else if(/^video\//.test(mime)){
            let video = document.createElement("video");
            video.src = src;
            video.style = "max-width: 100px; max-height: 100px;";
            video.setAttribute("preload", "metadata");
            video.setAttribute('data-dp', src);
            video.setAttribute('data-caption', link.text);
            video.addEventListener('click', click_OpenThumbnail);
            link.insertBefore(video, link.firstChild);
        }
    })
});
</script>

実行結果

Redmineファイルプレビュー機能の強化.gif

メリット

  • Redmineサーバーに入る必要がないため、サーバーのログイン権限が無い人でも組込みや更新ができる。
  • GitやSVNの運用フローや更新の仕組みを考える必要が無いため手頃に実現できる。

デメリット

OSSのソースコードは上書き更新できない

Redmineの添付ファイルは上書きできないためJavaScriptのOSS更新時には *.js の上げ直しと、View Customize側での<script>タグのsrc属性の更新が必要になる。頻繁に更新されるものには不向き。

ページの読み込み負荷が上がる

Redmineの添付ファイルはブラウザキャッシュされない設定になっているらしく、リクエスト事にファイルの通信が発生してしまう

特に従量課金のクラウドサーバーやパフォーマンスにシビアな大規模Redmineでは、巨大なJavaScriptや補助のデータファイルを大量に組み込みたい場合に問題になる可能性がある

そういったケースではこの添付ファイルを利用した方法は使わずに、ブラウザがファイルをキャッシュするようにCDNにファイルを置いたり、ファイルをGit等で管理して更新はJenkins等で自動検知してRedmineの /public ディレクトリ下へ展開してそのURLを組み込む等の仕組みが必要になる。

12
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?