日本でのアトラシアン(Atlassian)製品導入No.1

  1. HOME

リックソフトブログ

2013/01/08

Confluenceをカスタマイズして会社ブログを作ってみた

Share on FacebookShare on Google+Tweet about this on TwitterShare on LinkedIn

Author

大貫 浩Hiroshi Ohnuki

大貫 浩

お正月休みを使って、Confluenceをカスタマイズして会社ブログ、Ricksoftブログ(このサイト)を作りました。この記事ではどのようにConfluenceをカスタマイズし、このブログを作ったのかを説明します。

WS000050

このRicksoftブログ作成までの過程を報告します。まず、ブログ開発で目指した目標は以下の3点です。

作業は上記設定した目標の順番に行いました。と、そのまえに会社ブログに合うようにセキュリティ設定を行います。

事前セキュリティ設定

Confluenceは1インスタンスで複数のスペース(情報の入れ物)を持てます。そしてその単位にセキュリティ設定(権限設定)ができます。まずブログ用スペースを会社ブログに合うようにセキュリティ設定します。設定は簡単でスペース管理権限で、ログインユーザにブログ作成権限等を与え、匿名ユーザーには表示とコメント追加権限を与えます。設定画面は以下のようになります。

WS000300

追加コスト無しで、それなりに見えるレベルのブログにする

まず手をいれるのは「スペースの管理」のルックアンドフィールです。ここでテーマを「グローバルなルック アンド フィール」にします。これによりHTMLレベルでのカスタマイズが可能になります。

WS000000

で、実際にHTMLレベルでカスタマイズする際は上記メニューの「レイアウト」からカスタマイズしたい部分を選びます。いろいろな部分をカスタマイズできますが、ほとんどのカスタマイズは「メイン レイアウト」で事足りるはずです。

WS000001

今回カスタマイズした「メイン レイアウト」は以下の通り。このファイルを読み解くポイントは、以下の点になります。

  • から始まって、で終わっているので、HTML全体をカスタマイズできる
  • に追加したいJavaScriptライブライやCSSファイルもここで書けばOK
  • ##はコメント
  • #if … #else …. #end 文が書ける(4行目)
  • < div >など好きなHTMLを書ける
  • id=…から、実際のページと対応する場所を見つけ出す(id値はHTMLページ内でユニークになる決まりがあるため見つけやすい。FireBugなどのWebブラウザの開発者向け機能は必須。)
  • $から始まる変数のようなものがある。(5行目の$sitemeshPage.getProperty()など)
  • そして注意しなければいけないのは、カッコ()の数がズレると、ページ描画に失敗して、復旧が困難になります。なぜなら、このスペース管理画面も「メイン レイアウト」を使って描画されているからです。なので、このメインレイアウトを編集する作業は必ずテスト環境で行ってください。自分は今まで生きてきて1回も失敗したことがないという人は別ですが…

    メインレイアウト

    <!DOCTYPE html>
    <html>
    <head>
        #if ($sitemeshPage.getProperty("page.spacename"))
            <title>$title - $sitemeshPage.getProperty("page.spacename") - #siteTitle()</title>
        #else
            <title>$title - #siteTitle()</title>
        #end
        #requireResource("confluence.web.resources:print-styles")
        #requireResourcesForContext("main")
        #requireResourcesForContext("atl.general")
        #parse("/decorators/includes/header.vm")
        $!settingsManager.globalSettings.customHtmlSettings.beforeHeadEnd
        $!sitemeshPage.getProperty("page.canonical")
    </head>
    ## HTML HEADER ENDS
         
    ## HTML BODY BEGINS
    <body #onLoadAttr() id="com-atlassian-confluence" class="$!theme.bodyClass $!sitemeshPage.getProperty("page.bodyClass")">
    <div id="fb-root"></div>
    <script>(function(d, s, id) {
      var js, fjs = d.getElementsByTagName(s)[0];
      if (d.getElementById(id)) return;
      js = d.createElement(s); js.id = id;
      js.src = "//connect.facebook.net/ja_JP/all.js#xfbml=1";
      fjs.parentNode.insertBefore(js, fjs);
    }(document, 'script', 'facebook-jssdk'));</script>
    #parse ("/decorators/includes/main-content-includes.vm")
    <ul id="assistive-skip-links" class="assistive">
        <li><a href="#title-heading">$action.getText("assistive.skiplink.to.content")</a></li>
        <li><a href="\#breadcrumbs">$action.getText("assistive.skiplink.to.breadcrumbs")</a></li>
        <li><a href="#header-menu-bar">$action.getText("assistive.skiplink.to.header.menu")</a></li>
        <li><a href="#navigation">$action.getText("assistive.skiplink.to.action.menu")</a></li>
        <li><a href="#quick-search-query">$action.getText("assistive.skiplink.to.quick.search")</a></li>
    </ul>
    <div id="page">
    <div id="full-height-container">
        #if($sitemeshPage.getProperty("page.tree"))
            #set($sidebarSettings = $studioSidebarHelper.getSettings($spaceKey))
            <div id="splitter">
            <div id="splitter-sidebar">
                $!sitemeshPage.getProperty("page.theme-navigation")
                 #if ($!sidebarSettings.isTreeEnabled() == "true")
                    $!sitemeshPage.getProperty("page.tree")
                #end
            </div>
            <div id="splitter-content">
            ## script needs to be executed here to prevent jerky content
            #includePluginJavascript("com.atlassian.confluence.plugins.doctheme:resources", "doc-theme.js")
            #if ($!sitemeshPage.getProperty("page.theme-header"))
                $!sitemeshPage.getProperty("page.theme-header")
            #end
        #end
        <!-- \#header -->
        <div id="header_rsblog" style="height: 56px; background-color: #EEE; border-bottom: solid 2px #326CA6; margin-bottom: 10px;">
          <div id="header_rsblog_inside" style="width: 960px; margin: 15px auto 2px;">
            <div style="float:right; ">
      <input type="hidden" name="spaceSearch" value="true" />
    #customQuickSearch("blog" true true [{"name":"where", "value":"BLOG"}])
    ##          #quickSearch("space=BLOG")
            </div>
            #logoBlock($spaceKey)
          </div>
        </div>
        ## CONTENT DIV BEGINS
        <div id="main" class="$!personalClass" style="width: 960px; margin: 0 auto;">
            <div id="sidebar-container" style="float:right; ">
            #if($showPersonalSidebar)
                #if ($sitemeshPage.getProperty("page.personal-sidebar"))
                    #skiplink("sidebar" $i18n.getText("assistive.skiplink.to.sidebar.start") $i18n.getText("assistive.skiplink.to.sidebar.end"))
                        $sitemeshPage.getProperty("page.personal-sidebar")
                    #end
                #end
            #else
                #if ($sitemeshPage.getProperty("page.blog-sidebar"))
                    #skiplink("sidebar" $i18n.getText("assistive.skiplink.to.sidebar.start") $i18n.getText("assistive.skiplink.to.sidebar.end"))
                        <div id="blog-sidebar" class="sidebar" >
                            $!sitemeshPage.getProperty("page.blog-sidebar")
                        </div><!-- \#blog-sidebar -->
                    #end
                #end
                #if ($sitemeshPage.getProperty("page.sidebar"))
                    #skiplink("sidebar" $i18n.getText("assistive.skiplink.to.sidebar.start") $i18n.getText("assistive.skiplink.to.sidebar.end"))
                        <div id="sidebar">
                            $!sitemeshPage.getProperty("page.sidebar")
                        </div><!-- \#sidebar -->
                    #end
                #end
            #end
            </div><!-- \#sidebar-container -->
            <div id="main-header">
                $!sitemeshPage.getProperty("page.content-navigation")
                $!sitemeshPage.getProperty("global.dashboard-navigation")
                #if ($sitemeshPage.getProperty("page.surtitle"))
                  #set($creatorName = $sitemeshPage.getProperty("page.surtitle"))
                  #set($creatorName = $stringUtils.substringAfter($creatorName , "name"))
                  #set($creatorName = $stringUtils.substringBetween($creatorName , '"', '"'))
                  #set ($tildeUsername= "~$creatorName")
                #end
                #if ($sitemeshPage.getProperty("page.postingDay"))
                  <h1 id="title-heading" class="pagetitle" style="padding-bottom: 10px; margin-bottom: 10px; border-bottom: 2px solid #196DB4;">
                    <span id="title-text">
                      $title
                    </span>
                  </h1>
                  <div style="float:left; margin-right: 15px;">
                    #logoBlock($tildeUsername)
                  </div>
                  <div style="display: inline; margin:0 30px 0 0;">
                    $sitemeshPage.getProperty("page.surtitle")
                  </div>
                  <ul style="margin: 5px; list-style-type: none;">
                    <li style="display: inline;">
                      <div class="fb-like" data-send="false" data-layout="button_count" data-width="450" data-show-faces="false"></div>
                    </li>
                    <li style="display: inline; position: relative;top: 3px;">
                      <a href="https://twitter.com/share" class="twitter-share-button" data-lang="ja" data-hashtags="rsblog">ツイート</a>
                      <script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0];if(!d.getElementById(id)) {js=d.createElement(s);js.id=id;js.src="//platform.twitter.com/widgets.js";fjs.parentNode.insertBefore(js,fjs);}}(document,"script","twitter-wjs");</script>
                    </li>
                  </ul>
                  <div style="height:20px;">
                  </div>
                #end
            </div><!-- \#main-header -->
            $body
            <br class="clear">
        </div><!-- \#main -->
        ## CONTENT DIV ENDS
        #if($sitemeshPage.getProperty("page.tree"))
            $!sitemeshPage.getProperty("page.theme-footer")
            </div>
        </div>
        #end
    #webPanelForLocation("atl.footer" $action.context)
    ## $!sitemeshPage.getProperty("page.coherence-copyright")
    ## #foreach($property in $sitemeshPage.getPropertyKeys())
    ##      $property = $!sitemeshPage.getProperty($property)<br />
    ## #end
    </div><!-- \#full-height-container -->
    </div><!-- \#page -->
    </body>
    </html>
    

    ところで、上で書いた$変数とは何なのでしょう?実はこれはJavaオブジェクト変数です。

    例えば $sitemeshPage.getProperty() という記述が多く出てきますが、sitemeshPageは com.opensymphony.module.sitemesh.Page クラスのJavaオブジェクト変数です。これらは velocity_implicit.vm ファイル(下記参照)に定義されています。(ただし、このファイルに定義されている$変数が全て使えるわけではありません)

    そして$変数はJavaオブジェクトなので、メソッド呼び出しできます。例えばメインレイアウトの下から7行目付近の #foreach 文はKey=Valueで格納されている Property値を全て列挙するループ処理です。今はコメントアウトされていますが、画面描画で便利に使える値が無いか調べた名残です。

    この $sitemeshPage.getPropertyKeys() や $!sitemeshPage.getProperty($property) はJavaメソッド呼び出しです。

    com.opensymphony.module.sitemesh.Page クラスの JavaDocをネットで探すとメソッド仕様を見つけることができます。

    velocity_implicit.vm

    [root@localhost classes]# pwd
    /opt/atlassian/confluence/confluence/WEB-INF/classes
    [root@localhost classes]# more velocity_implicit.vm
    #* @implicitly included *#
    #* @vtlvariable name="actionErrors" type="java.util.Collection<java.lang.String>" *#
    .....
    #* @vtlvariable name="helper" type="com.atlassian.confluence.themes.GlobalHelper" *#
    #* @vtlvariable name="i18n" type="com.atlassian.confluence.util.i18n.I18NBean" *#
    ...
    #* @vtlvariable name="req" type="javax.servlet.http.HttpServletRequest" *#
    #* @vtlvariable name="settingsManager" type="com.atlassian.confluence.setup.settings.SettingsManager" *#
    #* @vtlvariable name="sitemeshPage" type="com.opensymphony.module.sitemesh.Page" *#
    #* @vtlvariable name="staticResourceUrlPrefix" type="java.lang.String" *#
    #* @vtlvariable name="stringUtils" type="org.apache.commons.lang.StringUtils" *#
    ...
    

    HTML以外にもCSSを追加することができます。そのためには「ルックアンドフィール」のスタイルシートをクリックします。以下のように書けます。ポイントは以下の点です。

    • 見せたくない部分をdisplay:noneで隠す
    • margin, padding で位置調整

    スタイルシート

    h1#title-heading {
      line-height: normal;
    }
    #navigation {
      display: none;
    }
    #space-blog-quick-search {
      display: none;
    }
    div#blog-sidebar {
      margin-top: 0;
    }
    h1.pagetitle {
      margin: 0;
    }
    div.blogSurtitle {
      display: inline;
      padding: 0;
      background-color: white;
      border-width: 0;
    }
    #content.space {
      margin-top: 0;
    }
    #content > div.page-metadata {
      display: none;
    }
    div.blog-post-listing  span.blogHeading a.blogHeading {
      font-size: 16pt;
    }
    div#content div.wiki-content,
    div#content div.wiki-content p,
    div#content div.wiki-content li {
      font-size: 10.5pt;
      line-height: 14pt;
    }
    
    記事のページに「いいね!」や「ツイート」を付ける

    私は今回ボタンをつけたいと思って調査して、初めて知ったんですが、「いいね!」ボタンや「ツイート」ボタンはFacebookやTwitterが機能を提供しているのですね。それらの実装方法がJavaScriptと分かったら、あとはメインレイアウトに貼りつけるだけ。

    • Facebookは20~27行目、114行目
    • Twitterは117~119行目

    Confluenceの使い勝手の良さを残す

    Confluenceの良さは何と言ってもエディターだと思ってます。このエディターも上のスタイルシートで下手な設定をすると正しく動かなくなるので注意です。

    今回の目的が会社ブログなので人気記事を自動表示する機能もつけたいと思いました。これはConfluenceに標準でインストールされているのですが、パフォーマンスが悪くなるという理由で初期状態OFFになっている Confluence Usage Stats というPluginを有効にしてマクロ一発で簡単に実現しました。

    あとはタグクラウドもマクロで表示できます。

    最後に

    Confluenceは標準で使っても十分な機能がありますが、カスタマイズを行うことでより目的に合ったカスタマイズを加えることができます。

    Share on FacebookShare on Google+Tweet about this on TwitterShare on LinkedIn

アトラシアン製品の導入と活用を
成功させたいなら
リックソフトのサポートが
必要です。

サードパーティ製のアドオンもサポート

サードパーティ製のアドオンもサポート

RS標準サポート

アトラシアン社ではサポート範囲外となっているサードパーティ製のアドオンをリックソフトのRS標準サポートではサポートします。

  • アトラシアン製品とサードパーティ製のアドオンとの事象の切り分け
  • 海外のアドオンベンダーとのやり取りを代行(日→英/英→日)

リックソフトのRS標準サポートは開発元が提供するサポート以上の価値があります。

サポートについて

ツールの活用を促進するアイテム

ツールの活用を促進するアイテム

各種ガイドブック

ツールを導入しただけでは成功とはいえません。利用者が効果を感じていただくことが大切です。独自で制作した各種ガイドブックはツール活用を促進します。

リックソフトからライセンス購入を頂いたお客様にはガイドブックを無料進呈いたします。

ガイドブックについて

価値あるツールの使い方

価値あるツールの使い方

研修・トレーニング

ツール操作の研修だけでなく「ウォータフォール型開発」「アジャイル型開発」のシミュレーション研修も提供。

日本随一の生産性向上にも効果のある研修サービスです。

リックソフトからライセンス購入を頂いたお客様には無料招待や割引特典がございます。

研修について

PAGE TOP